@zoralabs/coins 2.1.1 → 2.2.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.
- package/.turbo/turbo-build$colon$js.log +152 -0
- package/CHANGELOG.md +60 -0
- package/abis/BaseCoin.json +26 -0
- package/abis/BaseTest.json +2 -7
- package/abis/CoinConstants.json +0 -104
- package/abis/ContentCoin.json +26 -0
- package/abis/CreatorCoin.json +30 -4
- package/abis/FeeEstimatorHook.json +0 -5
- package/abis/ICoin.json +26 -0
- package/abis/ICoinV3.json +26 -0
- package/abis/ICreatorCoin.json +39 -0
- package/abis/IERC721.json +36 -36
- package/abis/IHasCoinType.json +15 -0
- package/abis/IHasTotalSupplyForPositions.json +15 -0
- package/abis/IZoraFactory.json +52 -0
- package/abis/IZoraHookRegistry.json +188 -0
- package/abis/VmContractHelper227.json +233 -0
- package/abis/ZoraFactoryImpl.json +32 -6
- package/abis/ZoraHookRegistry.json +375 -0
- package/abis/{CreatorCoinHook.json → ZoraV4CoinHook.json} +1 -1
- package/addresses/8453.json +2 -1
- package/dist/index.cjs +72 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +72 -10
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +90 -10
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/foundry.toml +4 -1
- package/package/wagmiGenerated.ts +72 -10
- package/package.json +7 -5
- package/script/PrintRegisterUpgradePath.s.sol +0 -7
- package/script/TestBackingCoinSwap.s.sol +0 -1
- package/script/TestV4Swap.s.sol +0 -1
- package/script/UpgradeFactoryImpl.s.sol +1 -1
- package/src/BaseCoin.sol +15 -12
- package/src/ContentCoin.sol +10 -0
- package/src/CreatorCoin.sol +28 -7
- package/src/ZoraFactoryImpl.sol +62 -23
- package/src/deployment/CoinsDeployerBase.sol +24 -58
- package/src/hook-registry/ZoraHookRegistry.sol +93 -0
- package/src/hooks/{BaseZoraV4CoinHook.sol → ZoraV4CoinHook.sol} +13 -8
- package/src/interfaces/ICoin.sol +19 -1
- package/src/interfaces/ICreatorCoin.sol +4 -0
- package/src/interfaces/IZoraFactory.sol +32 -10
- package/src/interfaces/IZoraHookRegistry.sol +47 -0
- package/src/libs/CoinConstants.sol +0 -32
- package/src/libs/CoinRewardsV4.sol +53 -15
- package/src/libs/CreatorCoinConstants.sol +0 -1
- package/src/libs/HooksDeployment.sol +13 -65
- package/src/libs/MarketConstants.sol +10 -12
- package/src/libs/V4Liquidity.sol +30 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/CoinUniV4.t.sol +33 -30
- package/test/ContentCoinRewards.t.sol +320 -0
- package/test/CreatorCoin.t.sol +1 -1
- package/test/CreatorCoinRewards.t.sol +375 -0
- package/test/DeploymentHooks.t.sol +10 -10
- package/test/Factory.t.sol +24 -7
- package/test/HooksDeployment.t.sol +4 -4
- package/test/LiquidityMigration.t.sol +4 -9
- package/test/Upgrades.t.sol +44 -48
- package/test/ZoraHookRegistry.t.sol +266 -0
- package/test/utils/BaseTest.sol +25 -42
- package/test/utils/FeeEstimatorHook.sol +4 -6
- package/test/utils/RewardTestHelpers.sol +106 -0
- package/.turbo/turbo-build.log +0 -199
- package/abis/AutoSwapperTest.json +0 -618
- package/abis/BadImpl.json +0 -15
- package/abis/BaseZoraV4CoinHook.json +0 -1664
- package/abis/CoinTest.json +0 -819
- package/abis/CoinUniV4Test.json +0 -1128
- package/abis/ContentCoinHook.json +0 -1733
- package/abis/CreatorCoinTest.json +0 -887
- package/abis/Deploy.json +0 -9
- package/abis/DeployHooks.json +0 -9
- package/abis/DeployScript.json +0 -35
- package/abis/DeployedCoinVersionLookupTest.json +0 -740
- package/abis/DifferentNamespaceVersionLookup.json +0 -39
- package/abis/FactoryTest.json +0 -748
- package/abis/FakeHookNoInterface.json +0 -21
- package/abis/GenerateDeterministicParams.json +0 -9
- package/abis/HooksDeploymentTest.json +0 -645
- package/abis/HooksTest.json +0 -709
- package/abis/InvalidLiquidityMigrationReceiver.json +0 -21
- package/abis/LiquidityMigrationReceiver.json +0 -103
- package/abis/LiquidityMigrationTest.json +0 -889
- package/abis/MockBadFactory.json +0 -15
- package/abis/MultiOwnableTest.json +0 -766
- package/abis/PrintUpgradeCommand.json +0 -9
- package/abis/TestDeployedCoinVersionLookupImplementation.json +0 -39
- package/abis/TestV4Swap.json +0 -9
- package/abis/UpgradeFactoryImpl.json +0 -9
- package/abis/UpgradeHooks.json +0 -35
- package/abis/UpgradesTest.json +0 -723
- package/src/hooks/ContentCoinHook.sol +0 -27
- package/src/hooks/CreatorCoinHook.sol +0 -27
- package/src/libs/CreatorCoinRewards.sol +0 -34
|
@@ -0,0 +1,47 @@
|
|
|
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
|
|
8
|
+
pragma solidity ^0.8.23;
|
|
9
|
+
|
|
10
|
+
interface IZoraHookRegistry {
|
|
11
|
+
/// @notice Zora hook data
|
|
12
|
+
struct ZoraHook {
|
|
13
|
+
address hook;
|
|
14
|
+
string tag;
|
|
15
|
+
string version;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// @notice Emitted when a hook is added to the registry
|
|
19
|
+
event ZoraHookRegistered(address indexed hook, string tag, string version);
|
|
20
|
+
|
|
21
|
+
/// @notice Emitted when a hook is removed from the registry
|
|
22
|
+
event ZoraHookRemoved(address indexed hook, string tag, string version);
|
|
23
|
+
|
|
24
|
+
/// @dev Reverts when the length of the hooks and tags arrays do not match
|
|
25
|
+
error ArrayLengthMismatch();
|
|
26
|
+
|
|
27
|
+
/// @notice Returns whether a hook is currently registered
|
|
28
|
+
function isRegisteredHook(address hook) external view returns (bool);
|
|
29
|
+
|
|
30
|
+
/// @notice Returns all registered hooks
|
|
31
|
+
function getHooks() external view returns (ZoraHook[] memory);
|
|
32
|
+
|
|
33
|
+
/// @notice Returns all registered hook addresses
|
|
34
|
+
function getHookAddresses() external view returns (address[] memory);
|
|
35
|
+
|
|
36
|
+
/// @notice Returns the tag for a hook
|
|
37
|
+
function getHookTag(address hook) external view returns (string memory);
|
|
38
|
+
|
|
39
|
+
/// @notice Returns the contract version for a hook if it exists
|
|
40
|
+
function getHookVersion(address hook) external view returns (string memory);
|
|
41
|
+
|
|
42
|
+
/// @notice Adds hooks to the registry
|
|
43
|
+
function registerHooks(address[] calldata hooks, string[] calldata tags) external;
|
|
44
|
+
|
|
45
|
+
/// @notice Removes hooks from the registry
|
|
46
|
+
function removeHooks(address[] calldata hooks) external;
|
|
47
|
+
}
|
|
@@ -24,38 +24,6 @@ library CoinConstants {
|
|
|
24
24
|
/// @dev Set to 0.0000001 ETH to prevent dust transactions
|
|
25
25
|
uint256 public constant MIN_ORDER_SIZE = 0.0000001 ether;
|
|
26
26
|
|
|
27
|
-
/// @notice The total fee percentage in basis points
|
|
28
|
-
/// @dev 100 basis points = 1%
|
|
29
|
-
uint256 public constant TOTAL_FEE_BPS = 100;
|
|
30
|
-
|
|
31
|
-
/// @notice The percentage of the total fee allocated to creators
|
|
32
|
-
/// @dev 5000 basis points = 50% of TOTAL_FEE_BPS
|
|
33
|
-
uint256 public constant TOKEN_CREATOR_FEE_BPS = 5000;
|
|
34
|
-
|
|
35
|
-
/// @notice The percentage of the total fee allocated to the protocol
|
|
36
|
-
/// @dev 2000 basis points = 20% of TOTAL_FEE_BPS
|
|
37
|
-
uint256 public constant PROTOCOL_FEE_BPS = 2000;
|
|
38
|
-
|
|
39
|
-
/// @notice The percentage of the total fee allocated to platform referrers
|
|
40
|
-
/// @dev 1500 basis points = 15% of TOTAL_FEE_BPS
|
|
41
|
-
uint256 public constant PLATFORM_REFERRER_FEE_BPS = 1500;
|
|
42
|
-
|
|
43
|
-
/// @notice The percentage of the total fee allocated to trade referrers
|
|
44
|
-
/// @dev 1500 basis points = 15% of TOTAL_FEE_BPS
|
|
45
|
-
uint256 public constant TRADE_REFERRER_FEE_BPS = 1500;
|
|
46
|
-
|
|
47
|
-
/// @notice The percentage of the LP fee allocated to creators
|
|
48
|
-
/// @dev 5000 basis points = 50% of the 1% LP FEE
|
|
49
|
-
uint256 public constant CREATOR_MARKET_REWARD_BPS = 5000;
|
|
50
|
-
|
|
51
|
-
/// @notice The percentage of the LP fee allocated to platform referrers
|
|
52
|
-
/// @dev 2500 basis points = 25% of the 1% LP FEE
|
|
53
|
-
uint256 public constant PLATFORM_REFERRER_MARKET_REWARD_BPS = 2500;
|
|
54
|
-
|
|
55
|
-
/// @notice The percentage of the LP fee allocated to the Doppler protocol
|
|
56
|
-
/// @dev 500 basis points = 5% of the 1% LP FEE
|
|
57
|
-
uint256 public constant DOPPLER_MARKET_REWARD_BPS = 500;
|
|
58
|
-
|
|
59
27
|
int24 internal constant DEFAULT_DISCOVERY_TICK_LOWER = -777000;
|
|
60
28
|
int24 internal constant DEFAULT_DISCOVERY_TICK_UPPER = 222000;
|
|
61
29
|
uint16 internal constant DEFAULT_NUM_DISCOVERY_POSITIONS = 10; // will be 11 total with tail position
|
|
@@ -29,28 +29,29 @@ import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
|
|
|
29
29
|
import {IHasSwapPath} from "../interfaces/ICoin.sol";
|
|
30
30
|
import {V4Liquidity} from "./V4Liquidity.sol";
|
|
31
31
|
import {UniV4SwapToCurrency} from "./UniV4SwapToCurrency.sol";
|
|
32
|
+
import {ICreatorCoinHook} from "../interfaces/ICreatorCoinHook.sol";
|
|
33
|
+
import {IHasCoinType} from "../interfaces/ICoin.sol";
|
|
34
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
35
|
+
import {ICreatorCoin} from "../interfaces/ICreatorCoin.sol";
|
|
32
36
|
|
|
33
37
|
library CoinRewardsV4 {
|
|
34
38
|
using SafeERC20 for IERC20;
|
|
35
39
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
uint256 public constant CREATOR_REWARD_BPS =
|
|
40
|
+
// Creator gets 62.5% of market rewards (0.50% of total 1% fee)
|
|
41
|
+
// Market rewards = 80% of total fee (0.80% of 1%)
|
|
42
|
+
uint256 public constant CREATOR_REWARD_BPS = 6250;
|
|
39
43
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
uint256 public constant CREATE_REFERRAL_REWARD_BPS = 1500;
|
|
44
|
+
// Platform referrer gets 25% of market rewards (0.20% of total 1% fee)
|
|
45
|
+
uint256 public constant CREATE_REFERRAL_REWARD_BPS = 2500;
|
|
43
46
|
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
uint256 public constant TRADE_REFERRAL_REWARD_BPS = 1500;
|
|
47
|
+
// Trade referrer gets 5% of market rewards (0.04% of total 1% fee)
|
|
48
|
+
uint256 public constant TRADE_REFERRAL_REWARD_BPS = 500;
|
|
47
49
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
uint256 public constant DOPPLER_REWARD_BPS = 500;
|
|
50
|
+
// Doppler gets 1.25% of market rewards (0.01% of total 1% fee)
|
|
51
|
+
uint256 public constant DOPPLER_REWARD_BPS = 125;
|
|
51
52
|
|
|
52
|
-
// LPs get
|
|
53
|
-
uint256 public constant LP_REWARD_BPS =
|
|
53
|
+
// LPs get 20% of total fee (0.20% of 1%)
|
|
54
|
+
uint256 public constant LP_REWARD_BPS = 2000;
|
|
54
55
|
|
|
55
56
|
function getTradeReferral(bytes calldata hookData) internal pure returns (address) {
|
|
56
57
|
return hookData.length >= 20 ? abi.decode(hookData, (address)) : address(0);
|
|
@@ -180,7 +181,13 @@ library CoinRewardsV4 {
|
|
|
180
181
|
/// @param fees The total amount of fees collected to be distributed
|
|
181
182
|
/// @param coin The coin contract instance that implements IHasRewardsRecipients to get recipient addresses
|
|
182
183
|
/// @param tradeReferrer The address of the trade referrer who should receive trade referral rewards (can be zero address)
|
|
183
|
-
function distributeMarketRewards(
|
|
184
|
+
function distributeMarketRewards(
|
|
185
|
+
Currency currency,
|
|
186
|
+
uint128 fees,
|
|
187
|
+
IHasRewardsRecipients coin,
|
|
188
|
+
address tradeReferrer,
|
|
189
|
+
IHasCoinType.CoinType coinType
|
|
190
|
+
) internal {
|
|
184
191
|
address payoutRecipient = coin.payoutRecipient();
|
|
185
192
|
address platformReferrer = coin.platformReferrer();
|
|
186
193
|
address protocolRewardRecipient = coin.protocolRewardRecipient();
|
|
@@ -219,6 +226,17 @@ library CoinRewardsV4 {
|
|
|
219
226
|
doppler,
|
|
220
227
|
marketRewards
|
|
221
228
|
);
|
|
229
|
+
|
|
230
|
+
if (coinType == IHasCoinType.CoinType.Creator) {
|
|
231
|
+
emit ICreatorCoinHook.CreatorCoinRewards(
|
|
232
|
+
address(coin),
|
|
233
|
+
Currency.unwrap(currency),
|
|
234
|
+
payoutRecipient,
|
|
235
|
+
protocolRewardRecipient,
|
|
236
|
+
rewards.creatorAmount,
|
|
237
|
+
rewards.protocolAmount
|
|
238
|
+
);
|
|
239
|
+
}
|
|
222
240
|
}
|
|
223
241
|
|
|
224
242
|
struct MarketRewards {
|
|
@@ -282,4 +300,24 @@ library CoinRewardsV4 {
|
|
|
282
300
|
function calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
|
|
283
301
|
return (amount * bps) / 10_000;
|
|
284
302
|
}
|
|
303
|
+
|
|
304
|
+
function getCoinType(IHasRewardsRecipients coin) internal view returns (IHasCoinType.CoinType) {
|
|
305
|
+
// first check if the coin supports the IHasCoinType interface - if it does, we can use that
|
|
306
|
+
if (IERC165(address(coin)).supportsInterface(type(IHasCoinType).interfaceId)) {
|
|
307
|
+
return IHasCoinType(address(coin)).coinType();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// see if its a legacy creator coin
|
|
311
|
+
return isLegacyCreatorCoin(coin) ? IHasCoinType.CoinType.Creator : IHasCoinType.CoinType.Content;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function isLegacyCreatorCoin(IHasRewardsRecipients coin) internal view returns (bool) {
|
|
315
|
+
// try to call the method `getClaimableAmount` on the legacy creator coin, if it succeeds, then it is a legacy creator coin,
|
|
316
|
+
// otherwise we can assume it is a content coin
|
|
317
|
+
try ICreatorCoin(address(coin)).getClaimableAmount() returns (uint256) {
|
|
318
|
+
return true;
|
|
319
|
+
} catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
285
323
|
}
|
|
@@ -9,7 +9,6 @@ pragma solidity ^0.8.23;
|
|
|
9
9
|
|
|
10
10
|
library CreatorCoinConstants {
|
|
11
11
|
uint256 internal constant TOTAL_SUPPLY = 1_000_000_000e18; // 1b coins
|
|
12
|
-
uint256 internal constant MARKET_SUPPLY = 500_000_000e18; // 500m coins
|
|
13
12
|
uint256 internal constant CREATOR_VESTING_SUPPLY = 500_000_000e18; // 500m coins
|
|
14
13
|
uint256 internal constant CREATOR_VESTING_DURATION = 5 * 365 days; // 5 years
|
|
15
14
|
address internal constant CURRENCY = 0x1111111111166b7FE7bd91427724B487980aFc69;
|
|
@@ -9,8 +9,7 @@ pragma solidity ^0.8.23;
|
|
|
9
9
|
|
|
10
10
|
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
|
|
11
11
|
import {Vm} from "forge-std/Vm.sol";
|
|
12
|
-
import {
|
|
13
|
-
import {CreatorCoinHook} from "../hooks/CreatorCoinHook.sol";
|
|
12
|
+
import {ZoraV4CoinHook} from "../hooks/ZoraV4CoinHook.sol";
|
|
14
13
|
import {HookMiner} from "@uniswap/v4-periphery/src/utils/HookMiner.sol";
|
|
15
14
|
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
16
15
|
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
|
|
@@ -83,26 +82,14 @@ library HooksDeployment {
|
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
function
|
|
85
|
+
function mineForCoinSalt(
|
|
87
86
|
address deployer,
|
|
88
87
|
address poolManager,
|
|
89
88
|
address coinVersionLookup,
|
|
90
89
|
address[] memory trustedMessageSenders,
|
|
91
90
|
address upgradeGate
|
|
92
91
|
) internal returns (address hookAddress, bytes32 salt) {
|
|
93
|
-
bytes memory hookCreationCode =
|
|
94
|
-
(salt, ) = mineAndCacheSalt(deployer, hookCreationCode);
|
|
95
|
-
hookAddress = HookMinerWithCreationCodeArgs.deterministicHookAddress(deployer, salt, hookCreationCode);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function mineForContentCoinSalt(
|
|
99
|
-
address deployer,
|
|
100
|
-
address poolManager,
|
|
101
|
-
address coinVersionLookup,
|
|
102
|
-
address[] memory trustedMessageSenders,
|
|
103
|
-
address upgradeGate
|
|
104
|
-
) internal returns (address hookAddress, bytes32 salt) {
|
|
105
|
-
bytes memory hookCreationCode = contentCoinCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
|
|
92
|
+
bytes memory hookCreationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
|
|
106
93
|
(salt, ) = mineAndCacheSalt(deployer, hookCreationCode);
|
|
107
94
|
hookAddress = HookMinerWithCreationCodeArgs.deterministicHookAddress(deployer, salt, hookCreationCode);
|
|
108
95
|
}
|
|
@@ -141,7 +128,7 @@ library HooksDeployment {
|
|
|
141
128
|
isDeployed = hookAddress.code.length > 0;
|
|
142
129
|
}
|
|
143
130
|
|
|
144
|
-
function
|
|
131
|
+
function hookConstructorArgs(
|
|
145
132
|
address poolManager,
|
|
146
133
|
address coinVersionLookup,
|
|
147
134
|
address[] memory trustedMessageSenders,
|
|
@@ -150,82 +137,43 @@ library HooksDeployment {
|
|
|
150
137
|
return abi.encode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
|
|
151
138
|
}
|
|
152
139
|
|
|
153
|
-
function
|
|
154
|
-
address poolManager,
|
|
155
|
-
address coinVersionLookup,
|
|
156
|
-
address[] memory trustedMessageSenders,
|
|
157
|
-
address upgradeGate
|
|
158
|
-
) internal pure returns (bytes memory) {
|
|
159
|
-
return
|
|
160
|
-
abi.encodePacked(
|
|
161
|
-
type(ContentCoinHook).creationCode,
|
|
162
|
-
contentCoinConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate)
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function creatorCoinCreationCode(
|
|
140
|
+
function makeHookCreationCode(
|
|
167
141
|
address poolManager,
|
|
168
142
|
address coinVersionLookup,
|
|
169
143
|
address[] memory trustedMessageSenders,
|
|
170
144
|
address upgradeGate
|
|
171
145
|
) internal pure returns (bytes memory) {
|
|
172
|
-
return
|
|
173
|
-
abi.encodePacked(
|
|
174
|
-
type(CreatorCoinHook).creationCode,
|
|
175
|
-
creatorCoinConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate)
|
|
176
|
-
);
|
|
146
|
+
return abi.encodePacked(type(ZoraV4CoinHook).creationCode, hookConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate));
|
|
177
147
|
}
|
|
178
148
|
|
|
179
149
|
/// @notice Deploys or returns existing ContentCoinHook using deterministic deployment. Ensures that if a hooks is already
|
|
180
150
|
/// deployed with the provided salt, it will be returned.
|
|
181
|
-
function
|
|
151
|
+
function deployZoraV4CoinHook(
|
|
182
152
|
address poolManager,
|
|
183
153
|
address coinVersionLookup,
|
|
184
154
|
address[] memory trustedMessageSenders,
|
|
185
155
|
address upgradeGate,
|
|
186
156
|
bytes32 salt
|
|
187
157
|
) internal returns (IHooks hook) {
|
|
188
|
-
bytes memory
|
|
189
|
-
return deployHookWithSalt(
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function creatorCoinConstructorArgs(
|
|
193
|
-
address poolManager,
|
|
194
|
-
address coinVersionLookup,
|
|
195
|
-
address[] memory trustedMessageSenders,
|
|
196
|
-
address upgradeGate
|
|
197
|
-
) internal pure returns (bytes memory) {
|
|
198
|
-
return abi.encode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function creatorCoinHookCreationCode(
|
|
202
|
-
address poolManager,
|
|
203
|
-
address coinVersionLookup,
|
|
204
|
-
address[] memory trustedMessageSenders,
|
|
205
|
-
address upgradeGate
|
|
206
|
-
) internal pure returns (bytes memory) {
|
|
207
|
-
return
|
|
208
|
-
abi.encodePacked(
|
|
209
|
-
type(CreatorCoinHook).creationCode,
|
|
210
|
-
creatorCoinConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate)
|
|
211
|
-
);
|
|
158
|
+
bytes memory creationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
|
|
159
|
+
return deployHookWithSalt(creationCode, salt);
|
|
212
160
|
}
|
|
213
161
|
|
|
214
162
|
address constant FOUNDRY_SCRIPT_ADDRESS = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
|
|
215
163
|
|
|
216
164
|
function deployHookWithExistingOrNewSalt(
|
|
217
165
|
address deployer,
|
|
218
|
-
bytes memory
|
|
166
|
+
bytes memory _hookCreationCode,
|
|
219
167
|
bytes32 salt
|
|
220
168
|
) internal returns (IHooks hook, bytes32 resultingSalt) {
|
|
221
|
-
(bool isDeployed, address existingHookAddress) = hooksIsDeployed(deployer,
|
|
169
|
+
(bool isDeployed, address existingHookAddress) = hooksIsDeployed(deployer, _hookCreationCode, salt);
|
|
222
170
|
|
|
223
171
|
if (isDeployed) {
|
|
224
172
|
hook = IHooks(existingHookAddress);
|
|
225
173
|
resultingSalt = salt;
|
|
226
174
|
} else {
|
|
227
|
-
(, resultingSalt) = mineForSalt(deployer,
|
|
228
|
-
hook = IHooks(Create2.deploy(0, resultingSalt,
|
|
175
|
+
(, resultingSalt) = mineForSalt(deployer, _hookCreationCode);
|
|
176
|
+
hook = IHooks(Create2.deploy(0, resultingSalt, _hookCreationCode));
|
|
229
177
|
}
|
|
230
178
|
}
|
|
231
179
|
}
|
|
@@ -3,23 +3,21 @@ pragma solidity ^0.8.23;
|
|
|
3
3
|
|
|
4
4
|
library MarketConstants {
|
|
5
5
|
/// @dev Constant used to increase precision during calculations
|
|
6
|
-
uint256 constant WAD = 1e18;
|
|
6
|
+
uint256 internal constant WAD = 1e18;
|
|
7
7
|
|
|
8
|
-
/// @notice The
|
|
9
|
-
/// @dev
|
|
10
|
-
|
|
8
|
+
/// @notice The number of coins allocated to the liquidity pool for content coins
|
|
9
|
+
/// @dev 990 million coins
|
|
10
|
+
uint256 internal constant CONTENT_COIN_MARKET_SUPPLY = 990_000_000 * WAD;
|
|
11
|
+
|
|
12
|
+
/// @notice The number of coins allocated to the liquidity pool for creator coins
|
|
13
|
+
/// @dev 500 million coins
|
|
14
|
+
uint256 internal constant CREATOR_COIN_MARKET_SUPPLY = 500_000_000 * WAD;
|
|
11
15
|
|
|
12
16
|
/// @notice The LP fee
|
|
13
|
-
/// @dev
|
|
14
|
-
uint24 internal constant LP_FEE_V4 =
|
|
17
|
+
/// @dev 10000 basis points = 1%
|
|
18
|
+
uint24 internal constant LP_FEE_V4 = 10_000;
|
|
15
19
|
|
|
16
20
|
/// @notice The spacing for 1% pools
|
|
17
21
|
/// @dev 200 ticks
|
|
18
22
|
int24 internal constant TICK_SPACING = 200;
|
|
19
|
-
|
|
20
|
-
/// @notice The minimum lower tick for legacy single LP WETH pools
|
|
21
|
-
int24 internal constant LP_TICK_LOWER_WETH = -208200;
|
|
22
|
-
|
|
23
|
-
/// @notice The upper tick for legacy single LP WETH pools
|
|
24
|
-
int24 internal constant LP_TICK_UPPER = 887200;
|
|
25
23
|
}
|
package/src/libs/V4Liquidity.sol
CHANGED
|
@@ -167,6 +167,12 @@ library V4Liquidity {
|
|
|
167
167
|
continue;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// skip lps with no fees to collect
|
|
171
|
+
(uint256 feeGrowthInside0DeltaX128, uint256 feeGrowthInside1DeltaX128) = getFeeGrowth(poolManager, poolKey, positions[i]);
|
|
172
|
+
if (feeGrowthInside0DeltaX128 == 0 && feeGrowthInside1DeltaX128 == 0) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
170
176
|
params = ModifyLiquidityParams({
|
|
171
177
|
tickLower: positions[i].tickLower,
|
|
172
178
|
tickUpper: positions[i].tickUpper,
|
|
@@ -221,6 +227,30 @@ library V4Liquidity {
|
|
|
221
227
|
liquidity = StateLibrary.getPositionLiquidity(poolManager, poolKey.toId(), positionId);
|
|
222
228
|
}
|
|
223
229
|
|
|
230
|
+
function getFeeGrowth(
|
|
231
|
+
IPoolManager poolManager,
|
|
232
|
+
PoolKey memory poolKey,
|
|
233
|
+
LpPosition memory position
|
|
234
|
+
) private view returns (uint256 feeGrowthInside0DeltaX128, uint256 feeGrowthInside1DeltaX128) {
|
|
235
|
+
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) = StateLibrary.getPositionInfo(
|
|
236
|
+
poolManager,
|
|
237
|
+
poolKey.toId(),
|
|
238
|
+
address(this),
|
|
239
|
+
position.tickLower,
|
|
240
|
+
position.tickUpper,
|
|
241
|
+
bytes32(0)
|
|
242
|
+
);
|
|
243
|
+
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = StateLibrary.getFeeGrowthInside(
|
|
244
|
+
poolManager,
|
|
245
|
+
poolKey.toId(),
|
|
246
|
+
position.tickLower,
|
|
247
|
+
position.tickUpper
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
feeGrowthInside0DeltaX128 = feeGrowthInside0X128 - feeGrowthInside0LastX128;
|
|
251
|
+
feeGrowthInside1DeltaX128 = feeGrowthInside1X128 - feeGrowthInside1LastX128;
|
|
252
|
+
}
|
|
253
|
+
|
|
224
254
|
function mintPositions(IPoolManager poolManager, PoolKey memory poolKey, LpPosition[] memory positions) internal returns (int128 amount0, int128 amount1) {
|
|
225
255
|
ModifyLiquidityParams memory params;
|
|
226
256
|
uint256 numPositions = positions.length;
|
|
@@ -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.
|
|
12
|
+
return "2.2.1";
|
|
13
13
|
}
|
|
14
14
|
}
|
package/test/CoinUniV4.t.sol
CHANGED
|
@@ -16,6 +16,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
16
16
|
import {LpPosition} from "../src/types/LpPosition.sol";
|
|
17
17
|
import {CoinCommon} from "../src/libs/CoinCommon.sol";
|
|
18
18
|
import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
|
|
19
|
+
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
20
|
+
import {MarketConstants} from "../src/libs/MarketConstants.sol";
|
|
19
21
|
import {IMsgSender} from "../src/interfaces/IMsgSender.sol";
|
|
20
22
|
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
|
|
21
23
|
import {toBalanceDelta, BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
@@ -57,13 +59,13 @@ contract CoinUniV4Test is BaseTest {
|
|
|
57
59
|
/// and then reverting the state after the swap
|
|
58
60
|
function _estimateLpFees(bytes memory commands, bytes[] memory inputs) internal returns (FeeEstimatorHook.FeeEstimatorState memory feeState) {
|
|
59
61
|
uint256 snapshot = vm.snapshot();
|
|
60
|
-
_deployFeeEstimatorHook(
|
|
62
|
+
_deployFeeEstimatorHook(address(hook));
|
|
61
63
|
|
|
62
64
|
// Execute the swap
|
|
63
65
|
uint256 deadline = block.timestamp + 20;
|
|
64
66
|
router.execute(commands, inputs, deadline);
|
|
65
67
|
|
|
66
|
-
feeState = FeeEstimatorHook(payable(address(
|
|
68
|
+
feeState = FeeEstimatorHook(payable(address(hook))).getFeeState();
|
|
67
69
|
|
|
68
70
|
vm.revertToState(snapshot);
|
|
69
71
|
}
|
|
@@ -74,14 +76,14 @@ contract CoinUniV4Test is BaseTest {
|
|
|
74
76
|
bytes[] memory inputs
|
|
75
77
|
) internal returns (BalanceDelta delta, SwapParams memory swapParams, uint160 sqrtPriceX96) {
|
|
76
78
|
uint256 snapshot = vm.snapshot();
|
|
77
|
-
_deployFeeEstimatorHook(
|
|
79
|
+
_deployFeeEstimatorHook(address(hook));
|
|
78
80
|
|
|
79
81
|
// Execute the swap
|
|
80
82
|
uint256 deadline = block.timestamp + 20;
|
|
81
83
|
router.execute(commands, inputs, deadline);
|
|
82
84
|
|
|
83
|
-
delta = FeeEstimatorHook(payable(address(
|
|
84
|
-
swapParams = FeeEstimatorHook(payable(address(
|
|
85
|
+
delta = FeeEstimatorHook(payable(address(hook))).getFeeState().lastDelta;
|
|
86
|
+
swapParams = FeeEstimatorHook(payable(address(hook))).getFeeState().lastSwapParams;
|
|
85
87
|
|
|
86
88
|
sqrtPriceX96 = PoolStateReader.getSqrtPriceX96(coinV4.getPoolKey(), poolManager);
|
|
87
89
|
|
|
@@ -98,15 +100,15 @@ contract CoinUniV4Test is BaseTest {
|
|
|
98
100
|
});
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
function test_deployContentCoin_verifyTotalSupplyAllocation() public {
|
|
104
|
+
address currency = address(mockERC20A);
|
|
105
|
+
_deployV4Coin(currency);
|
|
106
|
+
|
|
107
|
+
// Verify total supply equals maximum allowed
|
|
108
|
+
assertEq(coinV4.totalSupply(), CoinConstants.MAX_TOTAL_SUPPLY, "total supply");
|
|
109
|
+
assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), MarketConstants.CONTENT_COIN_MARKET_SUPPLY, 1000, "pool launch supply");
|
|
110
|
+
assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.CREATOR_LAUNCH_REWARD, "creator launch reward");
|
|
111
|
+
}
|
|
110
112
|
|
|
111
113
|
function test_estimateLpFees() public {
|
|
112
114
|
address currency = address(mockERC20A);
|
|
@@ -161,17 +163,18 @@ contract CoinUniV4Test is BaseTest {
|
|
|
161
163
|
FeeEstimatorHook.FeeEstimatorState memory newFeeState = _estimateLpFees(commands, inputs);
|
|
162
164
|
|
|
163
165
|
uint128 newFeeCoin = isCoinToken0 ? newFeeState.fees0 : newFeeState.fees1;
|
|
164
|
-
uint128 newFeeCurrency = isCoinToken0 ? newFeeState.fees1 : newFeeState.fees0;
|
|
166
|
+
// uint128 newFeeCurrency = isCoinToken0 ? newFeeState.fees1 : newFeeState.fees0;
|
|
165
167
|
|
|
166
168
|
assertGt(newFeeCoin, 0, "fee coin on second swap should be greater than 0");
|
|
167
169
|
// assertGt(newFeeCurrency, 0, "fee currency on second swap should be greater than 0"); // TODO confirm what this should be -- prev was assertEq(0) and test passed but error message was asserting greater than 0
|
|
168
170
|
assertGt(newFeeState.afterSwapCurrencyAmount, 0, "after swap fee currency on second swap should be greater than 0");
|
|
169
171
|
}
|
|
170
172
|
|
|
171
|
-
|
|
172
|
-
uint256 public constant
|
|
173
|
-
uint256 public constant
|
|
174
|
-
uint256 public constant
|
|
173
|
+
// Use the same constants as CoinRewardsV4.sol for consistency
|
|
174
|
+
uint256 public constant CREATOR_REWARD_BPS = 6250; // 62.5% of market rewards (0.50% of total 1% fee)
|
|
175
|
+
uint256 public constant CREATE_REFERRAL_REWARD_BPS = 2500; // 25% of market rewards (0.20% of total 1% fee)
|
|
176
|
+
uint256 public constant TRADE_REFERRAL_REWARD_BPS = 500; // 5% of market rewards (0.04% of total 1% fee)
|
|
177
|
+
uint256 public constant DOPPLER_REWARD_BPS = 125; // 1.25% of market rewards (0.01% of total 1% fee)
|
|
175
178
|
|
|
176
179
|
struct Rewards {
|
|
177
180
|
uint256 backing;
|
|
@@ -228,8 +231,8 @@ contract CoinUniV4Test is BaseTest {
|
|
|
228
231
|
return rewards;
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
function test_distributesMarketRewards(
|
|
232
|
-
|
|
234
|
+
function test_distributesMarketRewards(bool hasCreateReferral, bool hasTradeReferral) public {
|
|
235
|
+
uint64 amountIn = 2 ether;
|
|
233
236
|
address currency = address(mockERC20A);
|
|
234
237
|
address createReferral = hasCreateReferral ? makeAddr("createReferral") : address(0);
|
|
235
238
|
address tradeReferral = hasTradeReferral ? makeAddr("tradeReferral") : address(0);
|
|
@@ -282,15 +285,15 @@ contract CoinUniV4Test is BaseTest {
|
|
|
282
285
|
}
|
|
283
286
|
assertEq(coinV4.balanceOf(coinV4.protocolRewardRecipient()), 0, "protocol reward coin");
|
|
284
287
|
|
|
285
|
-
assertApproxEqAbs(mockERC20A.balanceOf(coinV4.payoutRecipient()), totalRewards.backing,
|
|
286
|
-
assertApproxEqAbs(mockERC20A.balanceOf(coinV4.dopplerFeeRecipient()), totalRewards.doppler,
|
|
288
|
+
assertApproxEqAbs(mockERC20A.balanceOf(coinV4.payoutRecipient()), totalRewards.backing, 5000, "backing reward currency");
|
|
289
|
+
assertApproxEqAbs(mockERC20A.balanceOf(coinV4.dopplerFeeRecipient()), totalRewards.doppler, 5000, "doppler reward currency");
|
|
287
290
|
if (hasCreateReferral) {
|
|
288
|
-
assertApproxEqAbs(mockERC20A.balanceOf(createReferral), totalRewards.createReferral,
|
|
291
|
+
assertApproxEqAbs(mockERC20A.balanceOf(createReferral), totalRewards.createReferral, 5000, "create referral reward currency");
|
|
289
292
|
}
|
|
290
293
|
if (hasTradeReferral) {
|
|
291
|
-
assertApproxEqAbs(mockERC20A.balanceOf(tradeReferral), totalRewards.tradeReferral,
|
|
294
|
+
assertApproxEqAbs(mockERC20A.balanceOf(tradeReferral), totalRewards.tradeReferral, 5000, "trade referral reward currency");
|
|
292
295
|
}
|
|
293
|
-
assertApproxEqAbs(mockERC20A.balanceOf(coinV4.protocolRewardRecipient()), totalRewards.protocol,
|
|
296
|
+
assertApproxEqAbs(mockERC20A.balanceOf(coinV4.protocolRewardRecipient()), totalRewards.protocol, 5000, "protocol reward currency");
|
|
294
297
|
}
|
|
295
298
|
|
|
296
299
|
function test_distributesMarketRewardsInEth() public {
|
|
@@ -349,8 +352,8 @@ contract CoinUniV4Test is BaseTest {
|
|
|
349
352
|
assertGt(trader.balance, 0, "trader should have received ETH back");
|
|
350
353
|
}
|
|
351
354
|
|
|
352
|
-
function test_swap_emitsCoinMarketRewardsV4(
|
|
353
|
-
|
|
355
|
+
function test_swap_emitsCoinMarketRewardsV4() public {
|
|
356
|
+
uint64 amountIn = 1 ether;
|
|
354
357
|
address currency = address(mockERC20A);
|
|
355
358
|
address createReferral = makeAddr("createReferral");
|
|
356
359
|
address tradeReferral = makeAddr("tradeReferral");
|
|
@@ -588,7 +591,7 @@ contract CoinUniV4Test is BaseTest {
|
|
|
588
591
|
currency1: isCoinToken0 ? Currency.wrap(address(notACoin)) : Currency.wrap(address(coinV4)),
|
|
589
592
|
fee: 3000,
|
|
590
593
|
tickSpacing: 60,
|
|
591
|
-
hooks: IHooks(address(
|
|
594
|
+
hooks: IHooks(address(hook))
|
|
592
595
|
});
|
|
593
596
|
|
|
594
597
|
// We need to prank the call to come from the non-coin contract
|
|
@@ -598,7 +601,7 @@ contract CoinUniV4Test is BaseTest {
|
|
|
598
601
|
vm.expectRevert(
|
|
599
602
|
abi.encodeWithSelector(
|
|
600
603
|
CustomRevert.WrappedError.selector,
|
|
601
|
-
address(
|
|
604
|
+
address(hook),
|
|
602
605
|
IHooks.afterInitialize.selector,
|
|
603
606
|
abi.encodeWithSelector(IZoraV4CoinHook.NotACoin.selector, address(notACoin)),
|
|
604
607
|
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
|