@zoralabs/coins 1.1.0 → 1.1.2

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 (68) hide show
  1. package/.turbo/turbo-build.log +102 -97
  2. package/CHANGELOG.md +14 -0
  3. package/LICENSE +90 -21
  4. package/abis/BaseCoin.json +6 -1
  5. package/abis/BaseZoraV4CoinHook.json +119 -0
  6. package/abis/Coin.json +6 -1
  7. package/abis/CoinTest.json +14 -0
  8. package/abis/CoinV4.json +6 -1
  9. package/abis/ContentCoinHook.json +119 -0
  10. package/abis/CreatorCoin.json +6 -1
  11. package/abis/CreatorCoinHook.json +119 -0
  12. package/abis/ERC165.json +21 -0
  13. package/abis/ERC165Upgradeable.json +44 -0
  14. package/abis/FeeEstimatorHook.json +119 -0
  15. package/abis/ICoin.json +5 -0
  16. package/abis/ICoinV3.json +5 -0
  17. package/abis/ICoinV4.json +5 -0
  18. package/abis/ICreatorCoin.json +5 -0
  19. package/abis/LiquidityMigrationTest.json +7 -0
  20. package/abis/UpgradeHooks.json +35 -0
  21. package/abis/UpgradesTest.json +21 -0
  22. package/addresses/8453.json +11 -7
  23. package/dist/index.cjs +6 -3
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.js +6 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/wagmiGenerated.d.ts +15 -3
  28. package/dist/wagmiGenerated.d.ts.map +1 -1
  29. package/package/wagmiGenerated.ts +6 -3
  30. package/package.json +1 -1
  31. package/script/DeployUpgradeGate.s.sol +1 -1
  32. package/script/PrintRegisterUpgradePath.s.sol +35 -0
  33. package/script/UpgradeCoinImpl.sol +1 -1
  34. package/script/UpgradeHooks.s.sol +23 -0
  35. package/src/BaseCoin.sol +32 -10
  36. package/src/Coin.sol +7 -1
  37. package/src/CoinV4.sol +9 -3
  38. package/src/CreatorCoin.sol +7 -1
  39. package/src/ZoraFactoryImpl.sol +7 -1
  40. package/src/deployment/CoinsDeployerBase.sol +29 -9
  41. package/src/hooks/BaseZoraV4CoinHook.sol +124 -4
  42. package/src/hooks/ContentCoinHook.sol +7 -1
  43. package/src/hooks/CreatorCoinHook.sol +7 -1
  44. package/src/hooks/HookUpgradeGate.sol +7 -1
  45. package/src/interfaces/ICoin.sol +3 -0
  46. package/src/libs/CoinCommon.sol +7 -1
  47. package/src/libs/CoinConfigurationVersions.sol +7 -1
  48. package/src/libs/CoinConstants.sol +7 -1
  49. package/src/libs/CoinDopplerMultiCurve.sol +7 -1
  50. package/src/libs/CoinRewards.sol +7 -1
  51. package/src/libs/CoinRewardsV4.sol +25 -5
  52. package/src/libs/CoinSetup.sol +7 -1
  53. package/src/libs/CreatorCoinConstants.sol +7 -1
  54. package/src/libs/CreatorCoinRewards.sol +7 -1
  55. package/src/libs/DopplerMath.sol +7 -1
  56. package/src/libs/HooksDeployment.sol +20 -1
  57. package/src/libs/PoolStateReader.sol +7 -1
  58. package/src/libs/UniV4SwapHelper.sol +7 -1
  59. package/src/libs/UniV4SwapToCurrency.sol +7 -1
  60. package/src/libs/V4Liquidity.sol +34 -1
  61. package/src/types/PoolConfiguration.sol +7 -1
  62. package/src/utils/AutoSwapper.sol +7 -1
  63. package/src/version/ContractVersionBase.sol +1 -1
  64. package/test/Coin.t.sol +23 -0
  65. package/test/LiquidityMigration.t.sol +27 -0
  66. package/test/Upgrades.t.sol +180 -1
  67. package/test/utils/BaseTest.sol +5 -1
  68. /package/script/{DeployHooks.s.sol → DeployPostDeploymentHooks.s.sol} +0 -0
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.28;
3
9
 
4
10
  import {CreatorCoinConstants} from "../libs/CreatorCoinConstants.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {IHooksUpgradeGate} from "../interfaces/IHooksUpgradeGate.sol";
@@ -23,6 +23,9 @@ struct PoolKeyStruct {
23
23
  }
24
24
 
25
25
  interface ICoin is IERC165, IERC7572, IDopplerErrors, IHasRewardsRecipients {
26
+ /// @notice Thrown when the name is required for the coin
27
+ error NameIsRequired();
28
+
26
29
  /// @notice Thrown when an operation is attempted with a zero address
27
30
  error AddressZero();
28
31
 
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {CoinConstants} from "./CoinConstants.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  library CoinConstants {
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {PoolConfiguration} from "../interfaces/ICoin.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.28;
3
9
 
4
10
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
@@ -79,6 +85,21 @@ library CoinRewardsV4 {
79
85
  lpRewardAmount = uint128(calculateReward(uint256(totalBackingAmount), LP_REWARD_BPS));
80
86
  }
81
87
 
88
+ function convertDeltaToPositiveUint128(int256 delta) internal pure returns (uint128) {
89
+ if (delta < 0) {
90
+ revert SafeCast.SafeCastOverflow();
91
+ }
92
+ return uint128(uint256(delta));
93
+ }
94
+
95
+ function getCurrencyZeroBalance(IPoolManager poolManager, PoolKey calldata key) internal view returns (uint128) {
96
+ return convertDeltaToPositiveUint128(TransientStateLibrary.currencyDelta(poolManager, address(this), key.currency0));
97
+ }
98
+
99
+ function getCurrencyOneBalance(IPoolManager poolManager, PoolKey calldata key) internal view returns (uint128) {
100
+ return convertDeltaToPositiveUint128(TransientStateLibrary.currencyDelta(poolManager, address(this), key.currency1));
101
+ }
102
+
82
103
  /// @notice Mints LP rewards by creating new liquidity positions from collected fees
83
104
  /// @dev Splits collected fees between LP rewards and market rewards, then mints new LP positions
84
105
  /// with the LP reward portion. The remaining amount becomes market rewards for distribution.
@@ -99,8 +120,6 @@ library CoinRewardsV4 {
99
120
  if (lpRewardAmount0 > 0) {
100
121
  _modifyLiquidity(poolManager, key, lpRewardAmount0, true);
101
122
  }
102
-
103
- marketRewardsAmount0 = uint128(uint256(TransientStateLibrary.currencyDelta(poolManager, address(this), key.currency0)));
104
123
  }
105
124
 
106
125
  if (fees1 > 0) {
@@ -108,9 +127,10 @@ library CoinRewardsV4 {
108
127
  if (lpRewardAmount1 > 0) {
109
128
  _modifyLiquidity(poolManager, key, lpRewardAmount1, false);
110
129
  }
111
-
112
- marketRewardsAmount1 = uint128(uint256(TransientStateLibrary.currencyDelta(poolManager, address(this), key.currency1)));
113
130
  }
131
+
132
+ marketRewardsAmount0 = getCurrencyZeroBalance(poolManager, key);
133
+ marketRewardsAmount1 = getCurrencyOneBalance(poolManager, key);
114
134
  }
115
135
 
116
136
  /// @notice Mints a single-sided LP position
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {PoolConfigurationV4} from "../interfaces/ICoin.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  library CreatorCoinConstants {
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.28;
3
9
 
4
10
  import {ICreatorCoinHook} from "../interfaces/ICreatorCoinHook.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: BUSL-1.1
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.24;
3
9
 
4
10
  import {IDopplerErrors} from "../interfaces/IDopplerErrors.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
@@ -157,6 +163,19 @@ library HooksDeployment {
157
163
  );
158
164
  }
159
165
 
166
+ function creatorCoinCreationCode(
167
+ address poolManager,
168
+ address coinVersionLookup,
169
+ address[] memory trustedMessageSenders,
170
+ address upgradeGate
171
+ ) internal pure returns (bytes memory) {
172
+ return
173
+ abi.encodePacked(
174
+ type(CreatorCoinHook).creationCode,
175
+ creatorCoinConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate)
176
+ );
177
+ }
178
+
160
179
  /// @notice Deploys or returns existing ContentCoinHook using deterministic deployment. Ensures that if a hooks is already
161
180
  /// deployed with the provided salt, it will be returned.
162
181
  function deployContentCoinHook(
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
@@ -48,6 +54,9 @@ library V4Liquidity {
48
54
  error InvalidCallbackId(uint8 callbackId);
49
55
 
50
56
  /// @notice Locks the pool, and mint initial positions to the hook
57
+ /// @param poolManager The pool manager.
58
+ /// @param poolKey The pool key.
59
+ /// @param positions The positions.
51
60
  function lockAndMint(IPoolManager poolManager, PoolKey memory poolKey, LpPosition[] memory positions) internal {
52
61
  bytes memory data = abi.encode(MINT_CALLBACK_ID, abi.encode(MintCallbackData({poolKey: poolKey, positions: positions})));
53
62
 
@@ -129,11 +138,35 @@ library V4Liquidity {
129
138
  return abi.encode(result);
130
139
  }
131
140
 
141
+ function generatePositionsFromMigratedLiquidity(
142
+ uint160 sqrtPriceX96,
143
+ BurnedPosition[] calldata migratedLiquidity
144
+ ) internal pure returns (LpPosition[] memory positions) {
145
+ positions = new LpPosition[](migratedLiquidity.length);
146
+
147
+ for (uint256 i = 0; i < migratedLiquidity.length; i++) {
148
+ uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
149
+ sqrtPriceX96,
150
+ TickMath.getSqrtPriceAtTick(migratedLiquidity[i].tickLower),
151
+ TickMath.getSqrtPriceAtTick(migratedLiquidity[i].tickUpper),
152
+ migratedLiquidity[i].amount0Received,
153
+ migratedLiquidity[i].amount1Received
154
+ );
155
+ positions[i] = LpPosition({liquidity: liquidity, tickLower: migratedLiquidity[i].tickLower, tickUpper: migratedLiquidity[i].tickUpper});
156
+ }
157
+ }
158
+
132
159
  function collectFees(IPoolManager poolManager, PoolKey memory poolKey, LpPosition[] storage positions) internal returns (int128 balance0, int128 balance1) {
133
160
  ModifyLiquidityParams memory params;
134
161
  uint256 numPositions = positions.length;
135
162
 
136
163
  for (uint256 i; i < numPositions; i++) {
164
+ // if there is no liquidity, skip
165
+ uint128 liquidity = getLiquidity(poolManager, address(this), poolKey, positions[i].tickLower, positions[i].tickUpper);
166
+ if (liquidity == 0) {
167
+ continue;
168
+ }
169
+
137
170
  params = ModifyLiquidityParams({
138
171
  tickLower: positions[i].tickLower,
139
172
  tickUpper: positions[i].tickUpper,
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.23;
3
9
 
4
10
  /// @notice The configuration of the pool
@@ -1,4 +1,10 @@
1
- // SPDX-License-Identifier: MIT
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
2
8
  pragma solidity ^0.8.28;
3
9
 
4
10
  import {ISwapRouter} from "@zoralabs/shared-contracts/interfaces/uniswap/ISwapRouter.sol";
@@ -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 "1.1.0";
12
+ return "1.1.2";
13
13
  }
14
14
  }
package/test/Coin.t.sol CHANGED
@@ -8,6 +8,8 @@ import {CoinConstants} from "../src/libs/CoinConstants.sol";
8
8
  import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
9
9
  import {IHasRewardsRecipients} from "../src/interfaces/IHasRewardsRecipients.sol";
10
10
  import {PoolConfiguration} from "../src/interfaces/ICoin.sol";
11
+ import {IERC165, IERC7572, ICoin, ICoinComments, IERC20} from "../src/BaseCoin.sol";
12
+ import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
11
13
 
12
14
  contract CoinTest is BaseTest {
13
15
  using stdJson for string;
@@ -16,6 +18,17 @@ contract CoinTest is BaseTest {
16
18
  super.setUp();
17
19
  }
18
20
 
21
+ function test_contract_ierc165_support() public {
22
+ _deployCoin();
23
+ assertEq(coin.supportsInterface(type(IZoraFactory).interfaceId), false);
24
+ assertEq(coin.supportsInterface(bytes4(0x00000000)), false);
25
+ assertEq(coin.supportsInterface(type(IERC165).interfaceId), true);
26
+ assertEq(coin.supportsInterface(type(IERC7572).interfaceId), true);
27
+ assertEq(coin.supportsInterface(type(ICoin).interfaceId), true);
28
+ assertEq(coin.supportsInterface(type(ICoinComments).interfaceId), true);
29
+ assertEq(coin.supportsInterface(type(IERC7572).interfaceId), true);
30
+ }
31
+
19
32
  function test_contract_version() public {
20
33
  _deployCoin();
21
34
  string memory package = vm.readFile("./package.json");
@@ -658,4 +671,14 @@ contract CoinTest is BaseTest {
658
671
  vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
659
672
  coin.setNameAndSymbol(newName, newSymbol);
660
673
  }
674
+
675
+ function test_update_metadata_reverts_if_name_is_blank() public {
676
+ _deployCoin();
677
+ string memory newName = "";
678
+ string memory newSymbol = "NEW";
679
+
680
+ vm.prank(users.creator);
681
+ vm.expectRevert(abi.encodeWithSelector(ICoin.NameIsRequired.selector));
682
+ coin.setNameAndSymbol(newName, newSymbol);
683
+ }
661
684
  }
@@ -123,6 +123,33 @@ contract LiquidityMigrationTest is BaseTest {
123
123
  assertEq(newPoolKey.tickSpacing, poolKey.tickSpacing, "poolkey tickSpacing");
124
124
  }
125
125
 
126
+ function test_migrateLiquidity_enablesSwapsOnOldPoolKey() public {
127
+ address currency = address(mockERC20A);
128
+ mockERC20A.mint(address(poolManager), 1_000_000_000 ether);
129
+ _deployV4Coin(currency);
130
+
131
+ address trader = makeAddr("trader");
132
+
133
+ mockERC20A.mint(trader, 10 ether);
134
+
135
+ // do some swaps
136
+ _swapSomeCurrencyForCoin(coinV4, currency, 1 ether, trader);
137
+ _swapSomeCoinForCurrency(coinV4, currency, uint128(coinV4.balanceOf(trader)), trader);
138
+
139
+ address newHook = address(new LiquidityMigrationReceiver());
140
+
141
+ PoolKey memory poolKey = coinV4.getPoolKey();
142
+
143
+ registerUpgradePath(address(poolKey.hooks), address(newHook));
144
+
145
+ // migrate the liquidity
146
+ vm.prank(users.creator);
147
+ coinV4.migrateLiquidity(address(newHook), "");
148
+
149
+ // now swap using the existing pool key, it should succeed
150
+ _swapSomeCurrencyForCoin(poolKey, coinV4, currency, uint128(mockERC20A.balanceOf(trader)), trader);
151
+ }
152
+
126
153
  function test_migrateLiquidity_emitsLiquidityMigrated() public {
127
154
  address currency = address(mockERC20A);
128
155
  _deployV4Coin(currency);
@@ -13,8 +13,20 @@ import {IWETH} from "../src/interfaces/IWETH.sol";
13
13
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
14
14
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
15
15
  import {BuySupplyWithSwapRouterHook} from "../src/hooks/deployment/BuySupplyWithSwapRouterHook.sol";
16
-
16
+ import {ContentCoinHook} from "../src/hooks/ContentCoinHook.sol";
17
17
  import {console} from "forge-std/console.sol";
18
+ import {IDeployedCoinVersionLookup} from "../src/interfaces/IDeployedCoinVersionLookup.sol";
19
+ import {IHooksUpgradeGate} from "../src/interfaces/IHooksUpgradeGate.sol";
20
+ import {HooksDeployment} from "../src/libs/HooksDeployment.sol";
21
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
22
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
23
+ import {MultiOwnable} from "../src/utils/MultiOwnable.sol";
24
+ import {CoinV4} from "../src/CoinV4.sol";
25
+ import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
26
+ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
27
+ import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
28
+ import {PoolStateReader} from "../src/libs/PoolStateReader.sol";
29
+ import {LpPosition} from "../src/types/LpPosition.sol";
18
30
 
19
31
  contract BadImpl {
20
32
  function contractName() public pure returns (string memory) {
@@ -159,4 +171,171 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
159
171
  // do some swaps to test out
160
172
  _swapSomeCoinForCurrency(ICoinV4(coinAddress), ZORA, uint128(IERC20(coinAddress).balanceOf(trader)), trader);
161
173
  }
174
+
175
+ address coinVersionLookup = 0x777777751622c0d3258f214F9DF38E35BF45baF3;
176
+ address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2;
177
+
178
+ function _swapSomeCurrencyForCoinAndExpectRevert(ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
179
+ uint128 minAmountOut = uint128(0);
180
+
181
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
182
+ currency,
183
+ amountIn,
184
+ address(_coin),
185
+ minAmountOut,
186
+ _coin.getPoolKey(),
187
+ bytes("")
188
+ );
189
+
190
+ vm.startPrank(trader);
191
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
192
+
193
+ // Execute the swap
194
+ uint256 deadline = block.timestamp + 20;
195
+ vm.expectRevert();
196
+ router.execute(commands, inputs, deadline);
197
+
198
+ vm.stopPrank();
199
+ }
200
+
201
+ function test_canCanFixBrokenContentCoinAndSwap() public {
202
+ vm.createSelectFork("base", 31835069);
203
+
204
+ address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
205
+
206
+ address contentCoin = 0x4E93A01c90f812284F71291a8d1415a904957156;
207
+
208
+ address creatorCoin = ICoinV4(contentCoin).currency();
209
+
210
+ uint256 amountIn = IERC20(creatorCoin).balanceOf(trader);
211
+
212
+ require(amountIn > 0, "no balance");
213
+
214
+ // this swap should revert because the content coin is broken
215
+ _swapSomeCurrencyForCoinAndExpectRevert(ICoinV4(contentCoin), creatorCoin, uint128(amountIn), trader);
216
+
217
+ bytes memory creationCode = HooksDeployment.contentCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
218
+
219
+ (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
220
+
221
+ // etch new hook into the content coin, it shouldn't revert anymore when swapping
222
+ vm.etch(address(ICoinV4(contentCoin).hooks()), address(newHook).code);
223
+
224
+ _swapSomeCurrencyForCoin(ICoinV4(contentCoin), creatorCoin, uint128(amountIn), trader);
225
+ }
226
+
227
+ function test_canUpgradeBrokenContentCoinAndSwap() public {
228
+ vm.createSelectFork("base", 31835069);
229
+
230
+ address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
231
+
232
+ address contentCoin = 0x4E93A01c90f812284F71291a8d1415a904957156;
233
+
234
+ address creatorCoin = ICoinV4(contentCoin).currency();
235
+
236
+ address existingHook = 0xd3D133469ADC85e01A4887404D8AC12d630e9040;
237
+
238
+ uint256 amountIn = IERC20(creatorCoin).balanceOf(trader);
239
+
240
+ bytes memory creationCode = HooksDeployment.contentCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
241
+
242
+ (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
243
+
244
+ address[] memory baseImpls = new address[](1);
245
+ baseImpls[0] = existingHook;
246
+
247
+ vm.prank(Ownable(upgradeGate).owner());
248
+ IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
249
+
250
+ vm.prank(MultiOwnable(contentCoin).owners()[0]);
251
+ CoinV4(contentCoin).migrateLiquidity(address(newHook), "");
252
+
253
+ // do some swaps to test out
254
+ _swapSomeCurrencyForCoin(ICoinV4(contentCoin), creatorCoin, uint128(amountIn), trader);
255
+ }
256
+
257
+ function getPositionInfo(
258
+ PoolKey memory key,
259
+ address owner,
260
+ int24 tickLower,
261
+ int24 tickUpper
262
+ ) internal view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) {
263
+ return StateLibrary.getPositionInfo(poolManager, key.toId(), owner, tickLower, tickUpper, bytes32(0));
264
+ }
265
+
266
+ function getLiquidityForPositions(PoolKey memory key, LpPosition[] memory positions) internal view returns (uint128[] memory liquidityForPositions) {
267
+ liquidityForPositions = new uint128[](positions.length);
268
+
269
+ for (uint256 i = 0; i < positions.length; i++) {
270
+ (uint128 liquidity, , ) = getPositionInfo(key, address(key.hooks), positions[i].tickLower, positions[i].tickUpper);
271
+ liquidityForPositions[i] = liquidity;
272
+ }
273
+ }
274
+
275
+ function getLiquidityForPoolCoin(ICoinV4 coin) internal view returns (uint128[] memory liquidityForPositions) {
276
+ return getLiquidityForPositions(coin.getPoolKey(), IZoraV4CoinHook(address(coin.hooks())).getPoolCoin(coin.getPoolKey()).positions);
277
+ }
278
+
279
+ function test_canUpgradeBrokenCreatorCoinAndSwap() public {
280
+ vm.createSelectFork("base", 31872861);
281
+
282
+ address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
283
+
284
+ ICoinV4 creatorCoin = ICoinV4(0x2F03aB8fD97F5874bc3274C296Bb954Ae92EdA34);
285
+
286
+ address zora = creatorCoin.currency();
287
+
288
+ address existingHook = address(creatorCoin.hooks());
289
+
290
+ bytes memory creationCode = HooksDeployment.creatorCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
291
+
292
+ (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
293
+
294
+ address[] memory baseImpls = new address[](1);
295
+ baseImpls[0] = existingHook;
296
+
297
+ vm.prank(Ownable(upgradeGate).owner());
298
+ IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
299
+
300
+ LpPosition[] memory beforePositions = IZoraV4CoinHook(address(creatorCoin.hooks())).getPoolCoin(creatorCoin.getPoolKey()).positions;
301
+ PoolKey memory beforeKey = creatorCoin.getPoolKey();
302
+
303
+ uint128[] memory beforeLiquidity = getLiquidityForPositions(beforeKey, beforePositions);
304
+ // get before price
305
+ uint160 beforePrice = PoolStateReader.getSqrtPriceX96(creatorCoin.getPoolKey(), poolManager);
306
+
307
+ vm.prank(MultiOwnable(address(creatorCoin)).owners()[0]);
308
+ CoinV4(address(creatorCoin)).migrateLiquidity(address(newHook), "");
309
+
310
+ // get liquidity of original positions after migration
311
+ uint128[] memory liquidityOfPositionsAfterMigration = getLiquidityForPositions(beforeKey, beforePositions);
312
+
313
+ // there should be no liquidity left in the original positions after migration
314
+ for (uint256 i = 0; i < liquidityOfPositionsAfterMigration.length; i++) {
315
+ assertEq(liquidityOfPositionsAfterMigration[i], 0);
316
+ }
317
+
318
+ // get liquidity of new positions after migration
319
+ PoolKey memory afterKey = creatorCoin.getPoolKey();
320
+ LpPosition[] memory afterPositions = IZoraV4CoinHook(address(afterKey.hooks)).getPoolCoin(afterKey).positions;
321
+ uint128[] memory afterLiquidity = getLiquidityForPositions(afterKey, afterPositions);
322
+
323
+ for (uint256 i = 0; i < beforeLiquidity.length; i++) {
324
+ // we added any extra liquidity to the last position, so we don't expect it to be the same
325
+ if (i != beforeLiquidity.length - 1) {
326
+ assertApproxEqAbs(beforeLiquidity[i], afterLiquidity[i], 200);
327
+ }
328
+ }
329
+
330
+ uint160 afterPrice = PoolStateReader.getSqrtPriceX96(creatorCoin.getPoolKey(), poolManager);
331
+
332
+ assertEq(beforePrice, afterPrice);
333
+
334
+ // make sure that the new hook has no balance of 0 or 1
335
+ assertApproxEqAbs(creatorCoin.getPoolKey().currency0.balanceOf(address(newHook)), 0, 10);
336
+ assertApproxEqAbs(creatorCoin.getPoolKey().currency1.balanceOf(address(newHook)), 0, 10);
337
+
338
+ // now try to swap some currency for the creator coin - it should succeed
339
+ _swapSomeCurrencyForCoin(creatorCoin, zora, uint128(IERC20(zora).balanceOf(trader) / 2), trader);
340
+ }
162
341
  }