@zoralabs/coins 2.4.0 → 2.4.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 (120) hide show
  1. package/.turbo/turbo-build$colon$js.log +116 -124
  2. package/CHANGELOG.md +6 -0
  3. package/abis/Address.json +0 -16
  4. package/abis/BuySupplyWithSwapRouterHook.json +0 -27
  5. package/abis/BuySupplyWithV4SwapHook.json +0 -32
  6. package/abis/Clones.json +1 -1
  7. package/abis/CoinDopplerMultiCurve.json +109 -0
  8. package/abis/Create2.json +0 -21
  9. package/abis/ERC1967Proxy.json +1 -1
  10. package/abis/ERC1967Utils.json +0 -45
  11. package/abis/{UpgradeCoinImpl.json → Errors.json} +14 -10
  12. package/abis/{MockERC20.json → IERC1363.json} +134 -104
  13. package/abis/IERC1967.json +47 -0
  14. package/abis/IERC20.json +0 -36
  15. package/abis/IProtocolRewards.json +0 -258
  16. package/abis/{Script.json → ISupportsLimitOrderFill.json} +2 -2
  17. package/abis/IZoraLimitOrderBookCoinsInterface.json +67 -0
  18. package/abis/IZoraV4CoinHook.json +10 -0
  19. package/abis/ProxyShim.json +15 -16
  20. package/abis/SafeCast.json +51 -0
  21. package/abis/{AddressConstants.json → SafeCast160.json} +1 -1
  22. package/abis/Strings.json +10 -0
  23. package/abis/UUPSUpgradeable.json +1 -1
  24. package/abis/V3ToV4SwapLib.json +28 -0
  25. package/abis/ZoraFactory.json +1 -1
  26. package/abis/ZoraFactoryImpl.json +22 -6
  27. package/abis/ZoraV4CoinHook.json +20 -48
  28. package/dist/index.cjs +950 -43
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +948 -41
  31. package/dist/index.js.map +1 -1
  32. package/dist/wagmiGenerated.d.ts +1459 -76
  33. package/dist/wagmiGenerated.d.ts.map +1 -1
  34. package/package/wagmiGenerated.ts +951 -44
  35. package/package.json +9 -9
  36. package/remappings.txt +2 -1
  37. package/src/ZoraFactoryImpl.sol +8 -0
  38. package/src/deployment/ForkedCoinsAddresses.sol +54 -0
  39. package/src/hooks/ZoraV4CoinHook.sol +74 -20
  40. package/src/hooks/deployment/BuySupplyWithV4SwapHook.sol +20 -142
  41. package/src/interfaces/ISupportsLimitOrderFill.sol +11 -0
  42. package/src/interfaces/IZoraLimitOrderBookCoinsInterface.sol +21 -0
  43. package/src/interfaces/IZoraV4CoinHook.sol +6 -0
  44. package/src/libs/CoinConstants.sol +6 -0
  45. package/src/libs/CoinDopplerMultiCurve.sol +1 -1
  46. package/src/libs/CoinRewardsV4.sol +0 -1
  47. package/src/libs/HooksDeployment.sol +20 -8
  48. package/src/libs/UniV4SwapHelper.sol +35 -0
  49. package/src/libs/V3ToV4SwapLib.sol +261 -0
  50. package/src/version/ContractVersionBase.sol +1 -1
  51. package/test/BuySupplyWithV4SwapHook.t.sol +4 -3
  52. package/test/Coin.t.sol +7 -1
  53. package/test/CoinUniV4.t.sol +2 -1
  54. package/test/ContentCoinRewards.t.sol +5 -1
  55. package/test/CreatorCoin.t.sol +3 -1
  56. package/test/CreatorCoinRewards.t.sol +3 -1
  57. package/test/Factory.t.sol +20 -7
  58. package/test/HooksDeployment.t.sol +16 -3
  59. package/test/LiquidityMigration.t.sol +52 -44
  60. package/test/MultiOwnable.t.sol +2 -1
  61. package/test/Upgrades.t.sol +110 -81
  62. package/test/V4Liquidity.t.sol +1 -1
  63. package/test/mocks/MockSwapRouter.sol +33 -0
  64. package/test/mocks/MockZoraLimitOrderBook.sol +14 -0
  65. package/test/utils/BaseTest.sol +14 -448
  66. package/test/utils/FeeEstimatorHook.sol +6 -2
  67. package/test/utils/V4TestSetup.sol +595 -0
  68. package/wagmi.config.ts +1 -1
  69. package/abis/BaseTest.json +0 -718
  70. package/abis/DeterministicDeployerAndCaller.json +0 -315
  71. package/abis/DeterministicUUPSProxyDeployer.json +0 -167
  72. package/abis/EIP712.json +0 -67
  73. package/abis/ERC20.json +0 -310
  74. package/abis/FeeEstimatorHook.json +0 -1938
  75. package/abis/IERC721.json +0 -287
  76. package/abis/IERC721Enumerable.json +0 -343
  77. package/abis/IERC721Metadata.json +0 -332
  78. package/abis/IERC721TokenReceiver.json +0 -36
  79. package/abis/IImmutableCreate2Factory.json +0 -93
  80. package/abis/IMulticall3.json +0 -440
  81. package/abis/ISafe.json +0 -15
  82. package/abis/ISymbol.json +0 -15
  83. package/abis/IUniswapV4Router04.json +0 -484
  84. package/abis/IUniversalRouter.json +0 -61
  85. package/abis/IV4Quoter.json +0 -310
  86. package/abis/ImmutableCreate2FactoryUtils.json +0 -15
  87. package/abis/LibString.json +0 -7
  88. package/abis/Math.json +0 -7
  89. package/abis/MockAirlock.json +0 -39
  90. package/abis/MockERC721.json +0 -350
  91. package/abis/ProtocolRewards.json +0 -494
  92. package/abis/ShortStrings.json +0 -18
  93. package/abis/SimpleERC20.json +0 -326
  94. package/abis/StdAssertions.json +0 -379
  95. package/abis/StdInvariant.json +0 -180
  96. package/abis/Test.json +0 -570
  97. package/abis/VmContractHelper235.json +0 -233
  98. package/abis/VmContractHelper242.json +0 -233
  99. package/abis/stdError.json +0 -119
  100. package/abis/stdStorageSafe.json +0 -52
  101. package/addresses/8453.json +0 -13
  102. package/addresses/84532.json +0 -10
  103. package/deterministicConfig/deployerAndCaller.json +0 -5
  104. package/deterministicConfig/zoraFactory.json +0 -8
  105. package/script/Deploy.s.sol +0 -23
  106. package/script/DeployAutoSwapper.s.sol +0 -30
  107. package/script/DeployDevFactory.s.sol +0 -21
  108. package/script/DeployPostDeploymentHooks.s.sol +0 -20
  109. package/script/DeployTrustedMsgSenderLookup.s.sol +0 -20
  110. package/script/DeployUpgradeGate.s.sol +0 -21
  111. package/script/GenerateDeterministicParams.s.sol +0 -43
  112. package/script/PrintRegisterUpgradePath.s.sol +0 -28
  113. package/script/PrintUpgradeCommand.s.sol +0 -13
  114. package/script/TestBackingCoinSwap.s.sol +0 -144
  115. package/script/TestV4Swap.s.sol +0 -133
  116. package/script/UpgradeCoinImpl.sol +0 -23
  117. package/script/UpgradeFactoryImpl.s.sol +0 -28
  118. package/script/UpgradeHooks.s.sol +0 -23
  119. package/src/deployment/CoinsDeployerBase.sol +0 -297
  120. /package/{test → src}/utils/ProxyShim.sol +0 -0
@@ -63,7 +63,7 @@ library HooksDeployment {
63
63
  bytes32 constant VALID_CREATOR_COIN_SALT = 0x00000000000000000000000000000000000000000000000000000000000031af;
64
64
 
65
65
  function mineForSalt(address deployer, bytes memory hookCreationCode) internal view returns (address hookAddress, bytes32 salt) {
66
- uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG) ^ (0x4444 << 144);
66
+ uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG) ^ (0x4444 << 144);
67
67
  return HookMinerWithCreationCodeArgs.find(deployer, flags, hookCreationCode);
68
68
  }
69
69
 
@@ -88,9 +88,11 @@ library HooksDeployment {
88
88
  address poolManager,
89
89
  address coinVersionLookup,
90
90
  ITrustedMsgSenderProviderLookup trustedMsgSenderLookup,
91
- address upgradeGate
91
+ address upgradeGate,
92
+ address orderFiller,
93
+ address hookRegistry
92
94
  ) internal returns (address hookAddress, bytes32 salt) {
93
- bytes memory hookCreationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate);
95
+ bytes memory hookCreationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate, orderFiller, hookRegistry);
94
96
  (salt, ) = mineAndCacheSalt(deployer, hookCreationCode);
95
97
  hookAddress = HookMinerWithCreationCodeArgs.deterministicHookAddress(deployer, salt, hookCreationCode);
96
98
  }
@@ -133,18 +135,26 @@ library HooksDeployment {
133
135
  address poolManager,
134
136
  address coinVersionLookup,
135
137
  ITrustedMsgSenderProviderLookup trustedMsgSenderLookup,
136
- address upgradeGate
138
+ address upgradeGate,
139
+ address orderFiller,
140
+ address hookRegistry
137
141
  ) internal pure returns (bytes memory) {
138
- return abi.encode(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate);
142
+ return abi.encode(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate, orderFiller, hookRegistry);
139
143
  }
140
144
 
141
145
  function makeHookCreationCode(
142
146
  address poolManager,
143
147
  address coinVersionLookup,
144
148
  ITrustedMsgSenderProviderLookup trustedMsgSenderLookup,
145
- address upgradeGate
149
+ address upgradeGate,
150
+ address orderFiller,
151
+ address hookRegistry
146
152
  ) internal pure returns (bytes memory) {
147
- return abi.encodePacked(type(ZoraV4CoinHook).creationCode, hookConstructorArgs(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate));
153
+ return
154
+ abi.encodePacked(
155
+ type(ZoraV4CoinHook).creationCode,
156
+ hookConstructorArgs(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate, orderFiller, hookRegistry)
157
+ );
148
158
  }
149
159
 
150
160
  /// @notice Deploys or returns existing ContentCoinHook using deterministic deployment. Ensures that if a hooks is already
@@ -154,9 +164,11 @@ library HooksDeployment {
154
164
  address coinVersionLookup,
155
165
  ITrustedMsgSenderProviderLookup trustedMsgSenderLookup,
156
166
  address upgradeGate,
167
+ address orderFiller,
168
+ address hookRegistry,
157
169
  bytes32 salt
158
170
  ) internal returns (IHooks hook) {
159
- bytes memory creationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate);
171
+ bytes memory creationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMsgSenderLookup, upgradeGate, orderFiller, hookRegistry);
160
172
  return deployHookWithSalt(creationCode, salt);
161
173
  }
162
174
 
@@ -22,6 +22,41 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
22
22
  import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
23
23
 
24
24
  library UniV4SwapHelper {
25
+ function buildExactInputMultiSwapCommand(
26
+ address currencyIn,
27
+ uint128 amountIn,
28
+ PoolKey[] memory keys,
29
+ uint128 minAmountOut,
30
+ bytes[] memory hopHookData
31
+ ) internal pure returns (bytes memory commands, bytes[] memory inputs) {
32
+ require(keys.length > 0 && hopHookData.length == keys.length, "invalid lengths");
33
+
34
+ PathKey[] memory path = new PathKey[](keys.length);
35
+
36
+ Currency currency = Currency.wrap(currencyIn);
37
+ Currency finalCurrencyOut;
38
+
39
+ for (uint256 i; i < keys.length; ++i) {
40
+ Currency out = currency == keys[i].currency0 ? keys[i].currency1 : keys[i].currency0;
41
+ path[i] = PathKey({intermediateCurrency: out, fee: keys[i].fee, tickSpacing: keys[i].tickSpacing, hooks: keys[i].hooks, hookData: hopHookData[i]});
42
+ currency = out;
43
+ finalCurrencyOut = out;
44
+ }
45
+
46
+ bytes memory actions = abi.encodePacked(uint8(Actions.SWAP_EXACT_IN), uint8(Actions.SETTLE), uint8(Actions.TAKE_ALL));
47
+ bytes[] memory params = new bytes[](3);
48
+ params[0] = abi.encode( // 1) SWAP_EXACT_IN({ currencyIn, path, amountIn, amountOutMinimum })
49
+ IV4Router.ExactInputParams({currencyIn: Currency.wrap(currencyIn), path: path, amountIn: amountIn, amountOutMinimum: minAmountOut})
50
+ );
51
+ params[1] = abi.encode(currencyIn, amountIn, true); // 2) SETTLE(tokenIn, amountIn, payerIsUser=true) — pulls from user via Permit2
52
+ params[2] = abi.encode(Currency.unwrap(finalCurrencyOut), minAmountOut); // 3) TAKE_ALL(finalCurrencyOut, minOut)
53
+
54
+ commands = abi.encodePacked(uint8(Commands.V4_SWAP));
55
+
56
+ inputs = new bytes[](1);
57
+ inputs[0] = abi.encode(actions, params);
58
+ }
59
+
25
60
  function buildExactInputSingleSwapCommand(
26
61
  address currencyIn,
27
62
  uint128 amountIn,
@@ -0,0 +1,261 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
+ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
7
+ import {IPoolManager, SwapParams} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
8
+ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
9
+ import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
10
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
11
+ import {ISwapRouter} from "../interfaces/ISwapRouter.sol";
12
+ import {Path} from "@zoralabs/shared-contracts/libs/UniswapV3/Path.sol";
13
+ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
14
+ import {SafeCast160} from "permit2/src/libraries/SafeCast160.sol";
15
+
16
+ /// @title V3ToV4SwapLib
17
+ /// @notice Shared library for executing V3-to-V4 swap routing
18
+ /// @dev Provides common functionality for:
19
+ /// - V3 route validation and connection to V4 routes
20
+ /// - Input currency validation and transfer (ETH vs ERC20)
21
+ /// - V3 swap execution via ISwapRouter.exactInput()
22
+ /// - V4 multi-hop swap execution
23
+ /// - Delta settlement with poolManager
24
+ /// - V3 route parsing utilities
25
+ library V3ToV4SwapLib {
26
+ using SafeERC20 for IERC20;
27
+ using BalanceDeltaLibrary for BalanceDelta;
28
+ using CurrencyLibrary for Currency;
29
+ using Path for bytes;
30
+ using SafeCast160 for uint256;
31
+
32
+ // ============ ERRORS ============
33
+
34
+ error InsufficientInputCurrency(uint256 inputAmount, uint256 availableAmount);
35
+ error V3RouteCannotStartWithInputCurrency();
36
+ error V3RouteDoesNotConnectToV4RouteStart();
37
+
38
+ // ============ STRUCTS ============
39
+
40
+ /// @notice Parameters for V3 swap execution
41
+ struct V3SwapParams {
42
+ bytes v3Route; // V3 route path
43
+ address inputCurrency; // Input currency (address(0) for ETH)
44
+ uint256 inputAmount; // Amount of input currency
45
+ address recipient; // Recipient of swap output
46
+ }
47
+
48
+ /// @notice Parameters for V4 multi-hop swap execution
49
+ struct V4SwapParams {
50
+ PoolKey[] v4Route; // Array of pool keys to swap through
51
+ uint256 amountIn; // Starting amount
52
+ Currency startingCurrency; // Starting currency
53
+ }
54
+
55
+ /// @notice Result from V4 multi-hop swap
56
+ struct V4SwapResult {
57
+ uint128 outputAmount; // Final output amount
58
+ Currency outputCurrency; // Final output currency
59
+ }
60
+
61
+ // ============ VALIDATION ============
62
+
63
+ /// @notice Validates that V3 route output connects to V4 route start
64
+ /// @param v3Route The V3 route path (empty if no V3 swap)
65
+ /// @param inputCurrency The input currency for the swap
66
+ /// @param v4Route The V4 route (first pool must accept V3 output or input currency)
67
+ function validateRoutes(bytes memory v3Route, address inputCurrency, PoolKey[] memory v4Route) internal pure {
68
+ if (v4Route.length == 0) {
69
+ return; // No V4 route to validate
70
+ }
71
+
72
+ // Determine what currency should be the input to the V4 route
73
+ address v4InputCurrency;
74
+ if (v3Route.length == 0) {
75
+ // No V3 swap - input currency should directly match V4 route start
76
+ v4InputCurrency = inputCurrency;
77
+ } else {
78
+ // V3 swap exists - V3 output should match V4 route start
79
+ v4InputCurrency = getV3RouteOutputCurrency(v3Route);
80
+ }
81
+
82
+ PoolKey memory firstPool = v4Route[0];
83
+
84
+ require(
85
+ v4InputCurrency == Currency.unwrap(firstPool.currency0) || v4InputCurrency == Currency.unwrap(firstPool.currency1),
86
+ V3RouteDoesNotConnectToV4RouteStart()
87
+ );
88
+ }
89
+
90
+ /// @notice Validates and transfers input currency from sender to contract
91
+ /// @param inputCurrency The input currency address (address(0) for ETH)
92
+ /// @param inputAmount The amount to transfer
93
+ /// @param from The address to transfer from
94
+ /// @param msgValue The msg.value sent with the transaction
95
+ function validateAndTransferInputCurrency(address inputCurrency, uint256 inputAmount, address from, uint256 msgValue) internal {
96
+ if (inputCurrency == address(0)) {
97
+ // ETH payment
98
+ require(msgValue == inputAmount, InsufficientInputCurrency(inputAmount, msgValue));
99
+ } else {
100
+ // ERC20 payment
101
+ uint256 allowanceAmount = IERC20(inputCurrency).allowance(from, address(this));
102
+ require(allowanceAmount >= inputAmount, InsufficientInputCurrency(inputAmount, allowanceAmount));
103
+
104
+ uint256 balanceAmount = IERC20(inputCurrency).balanceOf(from);
105
+ require(balanceAmount >= inputAmount, InsufficientInputCurrency(inputAmount, balanceAmount));
106
+
107
+ IERC20(inputCurrency).safeTransferFrom(from, address(this), inputAmount);
108
+ }
109
+ }
110
+
111
+ /// @notice Validates and transfers input currency from sender using Permit2
112
+ /// @param permit2 The Permit2 contract
113
+ /// @param inputCurrency The input currency address (address(0) for ETH)
114
+ /// @param inputAmount The amount to transfer
115
+ /// @param from The address to transfer from
116
+ /// @param to The address to transfer to (recipient)
117
+ /// @param msgValue The msg.value sent with the transaction
118
+ function permit2TransferFrom(IAllowanceTransfer permit2, address inputCurrency, uint256 inputAmount, address from, address to, uint256 msgValue) internal {
119
+ if (inputCurrency == address(0)) {
120
+ // ETH payment - no Permit2 needed
121
+ require(msgValue == inputAmount, InsufficientInputCurrency(inputAmount, msgValue));
122
+ } else {
123
+ // ERC20 payment via Permit2
124
+ require(msgValue == 0, InsufficientInputCurrency(0, msgValue));
125
+ permit2.transferFrom(from, to, inputAmount.toUint160(), inputCurrency);
126
+ }
127
+ }
128
+
129
+ // ============ V3 SWAP LOGIC ============
130
+
131
+ /// @notice Executes a V3 swap if v3Route is provided, otherwise returns input
132
+ /// @param swapRouter The Uniswap V3 swap router
133
+ /// @param params The V3 swap parameters
134
+ /// @return amountCurrency The amount received from V3 swap (or input if no swap)
135
+ /// @return currencyReceived The currency received (output of V3 or input if no swap)
136
+ function executeV3Swap(ISwapRouter swapRouter, V3SwapParams memory params) internal returns (uint256 amountCurrency, address currencyReceived) {
137
+ if (params.v3Route.length == 0) {
138
+ // No V3 swap needed - return input directly
139
+ return (params.inputAmount, params.inputCurrency);
140
+ }
141
+
142
+ // Handle ERC20 input - approve swapRouter to spend tokens
143
+ if (params.inputCurrency != address(0)) {
144
+ IERC20(params.inputCurrency).safeIncreaseAllowance(address(swapRouter), params.inputAmount);
145
+ }
146
+
147
+ // Build swap router call for exactInput
148
+ ISwapRouter.ExactInputParams memory swapParams = ISwapRouter.ExactInputParams({
149
+ path: params.v3Route,
150
+ recipient: params.recipient,
151
+ amountIn: params.inputAmount,
152
+ amountOutMinimum: 0 // Slippage protection should be handled at higher level
153
+ });
154
+
155
+ // Conditional value passing - ETH if inputCurrency is address(0), otherwise 0
156
+ uint256 value = params.inputCurrency == address(0) ? params.inputAmount : 0;
157
+ amountCurrency = swapRouter.exactInput{value: value}(swapParams);
158
+ currencyReceived = getV3RouteOutputCurrency(params.v3Route);
159
+ }
160
+
161
+ // ============ V4 SWAP LOGIC ============
162
+
163
+ /// @notice Executes a multi-hop V4 swap through multiple pools
164
+ /// @param poolManager The Uniswap V4 pool manager
165
+ /// @param params The V4 swap parameters
166
+ /// @return result The swap result containing output amount and currency
167
+ function executeV4MultiHopSwap(IPoolManager poolManager, V4SwapParams memory params) internal returns (V4SwapResult memory result) {
168
+ Currency currentCurrency = params.startingCurrency;
169
+ uint128 currentAmount = uint128(params.amountIn);
170
+
171
+ // Execute swaps through the route
172
+ for (uint256 i = 0; i < params.v4Route.length; i++) {
173
+ PoolKey memory poolKey = params.v4Route[i];
174
+
175
+ // Determine swap direction based on current currency
176
+ bool zeroForOne = currentCurrency == poolKey.currency0;
177
+
178
+ BalanceDelta delta = poolManager.swap(
179
+ poolKey,
180
+ SwapParams(zeroForOne, -(int128(currentAmount)), zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),
181
+ ""
182
+ );
183
+
184
+ // Extract output amount from delta
185
+ uint128 outputAmount = zeroForOne ? uint128(delta.amount1()) : uint128(delta.amount0());
186
+
187
+ // Update for next iteration
188
+ currentAmount = outputAmount;
189
+ currentCurrency = zeroForOne ? poolKey.currency1 : poolKey.currency0;
190
+ }
191
+
192
+ result.outputAmount = currentAmount;
193
+ result.outputCurrency = currentCurrency;
194
+ }
195
+
196
+ // ============ DELTA SETTLEMENT ============
197
+
198
+ /// @notice Settles currency deltas with the pool manager
199
+ /// @param poolManager The Uniswap V4 pool manager
200
+ /// @param inputCurrency The input currency to settle
201
+ /// @param outputCurrency The output currency to take
202
+ /// @param to The recipient of the output currency
203
+ /// @param inputAmount The amount of input currency to settle
204
+ /// @param outputAmount The amount of output currency to take
205
+ function settleDeltas(
206
+ IPoolManager poolManager,
207
+ Currency inputCurrency,
208
+ Currency outputCurrency,
209
+ address to,
210
+ uint256 inputAmount,
211
+ uint128 outputAmount
212
+ ) internal {
213
+ // Pay the input amount
214
+ if (inputCurrency.isAddressZero()) {
215
+ // For ETH, settle with msg.value
216
+ poolManager.settle{value: inputAmount}();
217
+ } else {
218
+ // For ERC20, sync and transfer
219
+ poolManager.sync(inputCurrency);
220
+ inputCurrency.transfer(address(poolManager), inputAmount);
221
+ poolManager.settle();
222
+ }
223
+
224
+ // Transfer the output amount to the recipient
225
+ poolManager.take(outputCurrency, to, outputAmount);
226
+ }
227
+
228
+ // ============ UTILITIES ============
229
+
230
+ /// @notice Gets the output currency from a V3 route path
231
+ /// @param path The V3 route path
232
+ /// @return tokenOut The output token address
233
+ function getV3RouteOutputCurrency(bytes memory path) internal pure returns (address tokenOut) {
234
+ if (path.length == 0) {
235
+ return address(0);
236
+ }
237
+
238
+ // Traverse to the end of the path to find the final token
239
+ bytes memory currentPath = path;
240
+
241
+ // Keep skipping tokens until we reach the final pool
242
+ while (currentPath.hasMultiplePools()) {
243
+ currentPath = currentPath.skipToken();
244
+ }
245
+
246
+ // The final segment contains the last pool, decode to get the output token
247
+ (, tokenOut, ) = currentPath.decodeFirstPool();
248
+ }
249
+
250
+ /// @notice Gets the input currency from a V3 route path
251
+ /// @param path The V3 route path
252
+ /// @return tokenIn The input token address
253
+ function getV3RouteInputCurrency(bytes memory path) internal pure returns (address tokenIn) {
254
+ if (path.length == 0) {
255
+ return address(0);
256
+ }
257
+
258
+ // Use Path library to get the input token (first token in the path)
259
+ (tokenIn, , ) = path.decodeFirstPool();
260
+ }
261
+ }
@@ -9,6 +9,6 @@ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersion
9
9
  contract ContractVersionBase is IVersionedContract {
10
10
  /// @notice The version of the contract
11
11
  function contractVersion() external pure override returns (string memory) {
12
- return "2.4.0";
12
+ return "2.4.1";
13
13
  }
14
14
  }
@@ -3,6 +3,7 @@ pragma solidity ^0.8.13;
3
3
 
4
4
  import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {BuySupplyWithV4SwapHook} from "../src/hooks/deployment/BuySupplyWithV4SwapHook.sol";
6
+ import {V3ToV4SwapLib} from "../src/libs/V3ToV4SwapLib.sol";
6
7
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7
8
  import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
8
9
  import {ICoin} from "../src/interfaces/ICoin.sol";
@@ -363,7 +364,7 @@ contract BuySupplyWithV4SwapHookTest is BaseTest {
363
364
  // Should revert with InsufficientInputCurrency
364
365
  vm.deal(users.creator, insufficientAmount);
365
366
  bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(ZORA);
366
- vm.expectRevert(abi.encodeWithSelector(BuySupplyWithV4SwapHook.InsufficientInputCurrency.selector, inputAmount, insufficientAmount));
367
+ vm.expectRevert(abi.encodeWithSelector(V3ToV4SwapLib.InsufficientInputCurrency.selector, inputAmount, insufficientAmount));
367
368
 
368
369
  vm.prank(users.creator);
369
370
  factory.deployWithHook{value: insufficientAmount}(
@@ -414,7 +415,7 @@ contract BuySupplyWithV4SwapHookTest is BaseTest {
414
415
 
415
416
  bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(creatorCoinAddress);
416
417
  // Should revert with InsufficientInputCurrency
417
- vm.expectRevert(abi.encodeWithSelector(BuySupplyWithV4SwapHook.InsufficientInputCurrency.selector, inputAmount, amountToApprove));
418
+ vm.expectRevert(abi.encodeWithSelector(V3ToV4SwapLib.InsufficientInputCurrency.selector, inputAmount, amountToApprove));
418
419
 
419
420
  vm.prank(users.creator);
420
421
  factory.deployWithHook(
@@ -454,7 +455,7 @@ contract BuySupplyWithV4SwapHookTest is BaseTest {
454
455
  bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(creatorCoinAddress);
455
456
 
456
457
  vm.prank(users.creator);
457
- vm.expectRevert(abi.encodeWithSelector(BuySupplyWithV4SwapHook.V3RouteDoesNotConnectToV4RouteStart.selector));
458
+ vm.expectRevert(abi.encodeWithSelector(V3ToV4SwapLib.V3RouteDoesNotConnectToV4RouteStart.selector));
458
459
  factory.deployWithHook{value: 1 ether}(
459
460
  users.creator, // payoutRecipient
460
461
  _getDefaultOwners(), // owners
package/test/Coin.t.sol CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {ISwapRouter} from "../src/interfaces/ISwapRouter.sol";
6
6
  import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
7
7
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
@@ -11,6 +11,12 @@ import {PoolConfiguration} from "../src/interfaces/ICoin.sol";
11
11
  import {IERC165, IERC7572, ICoin, ICoinComments, IERC20} from "../src/BaseCoin.sol";
12
12
  import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
13
13
  import {BaseCoin} from "../src/BaseCoin.sol";
14
+ import {stdJson} from "forge-std/StdJson.sol";
15
+ import {ContentCoin} from "../src/ContentCoin.sol";
16
+ import {ZoraFactoryImpl} from "../src/ZoraFactoryImpl.sol";
17
+ import {MockERC20} from "./mocks/MockERC20.sol";
18
+ import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
19
+ import {MultiOwnable} from "../src/utils/MultiOwnable.sol";
14
20
 
15
21
  contract CoinTest is BaseTest {
16
22
  using stdJson for string;
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
6
6
  import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
7
7
  import {IV4Quoter} from "@uniswap/v4-periphery/src/interfaces/IV4Quoter.sol";
@@ -18,6 +18,7 @@ import {CoinCommon} from "../src/libs/CoinCommon.sol";
18
18
  import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
19
19
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
20
20
  import {IMsgSender} from "../src/interfaces/IMsgSender.sol";
21
+ import {ContentCoin} from "../src/ContentCoin.sol";
21
22
  import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
22
23
  import {toBalanceDelta, BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
23
24
  import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {console} from "forge-std/console.sol";
6
6
 
7
7
  import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
@@ -12,6 +12,10 @@ import {RewardTestHelpers, RewardBalances} from "./utils/RewardTestHelpers.sol";
12
12
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
13
13
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
14
14
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
15
+ import {ContentCoin} from "../src/ContentCoin.sol";
16
+ import {CreatorCoin} from "../src/CreatorCoin.sol";
17
+ import {ICoin} from "../src/interfaces/ICoin.sol";
18
+ import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
15
19
 
16
20
  contract ContentCoinRewardsTest is BaseTest {
17
21
  ContentCoin internal contentCoin;
@@ -1,13 +1,15 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
 
6
6
  import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
7
7
  import {ICreatorCoinHook} from "../src/interfaces/ICreatorCoinHook.sol";
8
8
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
9
9
  import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
10
10
  import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
11
+ import {CreatorCoin} from "../src/CreatorCoin.sol";
12
+ import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
11
13
 
12
14
  contract CreatorCoinTest is BaseTest {
13
15
  CreatorCoin internal creatorCoin;
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {console} from "forge-std/console.sol";
6
6
 
7
7
  import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
@@ -13,7 +13,9 @@ import {FeeEstimatorHook} from "./utils/FeeEstimatorHook.sol";
13
13
  import {RewardTestHelpers, RewardBalances} from "./utils/RewardTestHelpers.sol";
14
14
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
15
15
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
16
+ import {CreatorCoin} from "../src/CreatorCoin.sol";
16
17
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
18
+ import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
17
19
 
18
20
  contract CreatorCoinRewardsTest is BaseTest {
19
21
  CreatorCoin internal creatorCoin;
@@ -1,10 +1,16 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
6
6
  import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
7
7
  import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
8
+ import {ZoraFactoryImpl} from "../src/ZoraFactoryImpl.sol";
9
+ import {ZoraFactory} from "../src/proxy/ZoraFactory.sol";
10
+ import {ContentCoin} from "../src/ContentCoin.sol";
11
+ import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
12
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
13
+ import {MockZoraLimitOrderBook} from "./mocks/MockZoraLimitOrderBook.sol";
8
14
 
9
15
  contract FactoryTest is BaseTest {
10
16
  function setUp() public override {
@@ -19,9 +25,16 @@ contract FactoryTest is BaseTest {
19
25
 
20
26
  // proxy initialization test
21
27
  address initialOwner = makeAddr("initialOwner");
22
- ZoraFactory factory = new ZoraFactory(address(impl));
23
- ZoraFactoryImpl(address(factory)).initialize(address(initialOwner));
24
- assertEq(ZoraFactoryImpl(address(factory)).owner(), initialOwner);
28
+ ZoraFactory newFactory = new ZoraFactory(address(impl));
29
+
30
+ // Add the new factory as an owner of the hook registry so it can register hooks during initialization
31
+ address[] memory newOwners = new address[](1);
32
+ newOwners[0] = address(newFactory);
33
+ vm.prank(users.factoryOwner);
34
+ zoraHookRegistry.addOwners(newOwners);
35
+
36
+ ZoraFactoryImpl(address(newFactory)).initialize(address(initialOwner));
37
+ assertEq(ZoraFactoryImpl(address(newFactory)).owner(), initialOwner);
25
38
  }
26
39
 
27
40
  function test_ownable2Step() public {
@@ -166,9 +179,9 @@ contract FactoryTest is BaseTest {
166
179
  address[] memory registeredHooks;
167
180
 
168
181
  registeredHooks = zoraHookRegistry.getHookAddresses();
169
- assertEq(registeredHooks.length, 0);
182
+ assertEq(registeredHooks.length, 1);
170
183
 
171
- _deployHooks(); // Deploys new content and creator coin hook addresses
184
+ _deployHooks(address(new MockZoraLimitOrderBook())); // Deploys new content and creator coin hook addresses
172
185
 
173
186
  // Deploy new factory impl with new content and creator coin hook addresses
174
187
  ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
@@ -177,7 +190,7 @@ contract FactoryTest is BaseTest {
177
190
  ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
178
191
 
179
192
  registeredHooks = zoraHookRegistry.getHookAddresses();
180
- assertEq(registeredHooks.length, 1);
193
+ assertEq(registeredHooks.length, 2);
181
194
  assertTrue(zoraHookRegistry.isRegisteredHook(address(hook)));
182
195
  }
183
196
  }
@@ -10,10 +10,12 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
10
10
  import {HookUpgradeGate} from "../src/hooks/HookUpgradeGate.sol";
11
11
  import {ITrustedMsgSenderProviderLookup} from "../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
12
12
  import {TrustedSenderTestHelper} from "./utils/TrustedSenderTestHelper.sol";
13
+ import {ZoraHookRegistry} from "../src/hook-registry/ZoraHookRegistry.sol";
13
14
 
14
15
  contract HooksDeploymentTest is Test, ContractAddresses {
15
16
  address internal hookUpgradeGate;
16
17
  ITrustedMsgSenderProviderLookup internal trustedMsgSenderLookup;
18
+ address internal mockHookRegistry;
17
19
  address internal owner;
18
20
  address internal nonOwner;
19
21
  address internal trustedSender1;
@@ -30,6 +32,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
30
32
  nonTrustedSender = makeAddr("nonTrustedSender");
31
33
 
32
34
  hookUpgradeGate = address(new HookUpgradeGate(makeAddr("factoryOwner")));
35
+ mockHookRegistry = makeAddr("mockHookRegistry");
33
36
 
34
37
  // Initialize with one trusted sender
35
38
  address[] memory initialTrustedSenders = new address[](1);
@@ -74,6 +77,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
74
77
  function test_canDeployContentCoinHookFromScript() public {
75
78
  vm.createSelectFork("base", 31653138);
76
79
 
80
+ address mockOrderFiller = makeAddr("mockOrderFiller");
77
81
  address[] memory trustedMessageSenders = new address[](0);
78
82
 
79
83
  ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
@@ -86,13 +90,17 @@ contract HooksDeploymentTest is Test, ContractAddresses {
86
90
  V4_POOL_MANAGER,
87
91
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
88
92
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
89
- hookUpgradeGate
93
+ hookUpgradeGate,
94
+ mockOrderFiller,
95
+ mockHookRegistry
90
96
  );
91
97
  IHooks hook = HooksDeployment.deployZoraV4CoinHook(
92
98
  V4_POOL_MANAGER,
93
99
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
94
100
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
95
101
  hookUpgradeGate,
102
+ mockOrderFiller,
103
+ mockHookRegistry,
96
104
  salt
97
105
  );
98
106
 
@@ -108,6 +116,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
108
116
  function test_canDeployCreatorCoinHookFromScript() public {
109
117
  vm.createSelectFork("base", 31653138);
110
118
 
119
+ address mockOrderFiller = makeAddr("mockOrderFiller");
111
120
  address[] memory trustedMessageSenders = new address[](0);
112
121
 
113
122
  ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
@@ -120,7 +129,9 @@ contract HooksDeploymentTest is Test, ContractAddresses {
120
129
  V4_POOL_MANAGER,
121
130
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
122
131
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
123
- hookUpgradeGate
132
+ hookUpgradeGate,
133
+ mockOrderFiller,
134
+ mockHookRegistry
124
135
  );
125
136
 
126
137
  IHooks hook = HooksDeployment.deployHookWithSalt(
@@ -128,7 +139,9 @@ contract HooksDeploymentTest is Test, ContractAddresses {
128
139
  V4_POOL_MANAGER,
129
140
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
130
141
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
131
- hookUpgradeGate
142
+ hookUpgradeGate,
143
+ mockOrderFiller,
144
+ mockHookRegistry
132
145
  ),
133
146
  salt
134
147
  );