@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,375 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import "./utils/BaseTest.sol";
|
|
5
|
+
import {console} from "forge-std/console.sol";
|
|
6
|
+
|
|
7
|
+
import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
|
|
8
|
+
import {ICreatorCoinHook} from "../src/interfaces/ICreatorCoinHook.sol";
|
|
9
|
+
import {IHasRewardsRecipients} from "../src/interfaces/IHasRewardsRecipients.sol";
|
|
10
|
+
import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
|
|
11
|
+
import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
|
|
12
|
+
import {FeeEstimatorHook} from "./utils/FeeEstimatorHook.sol";
|
|
13
|
+
import {RewardTestHelpers, RewardBalances} from "./utils/RewardTestHelpers.sol";
|
|
14
|
+
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
15
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
16
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
17
|
+
|
|
18
|
+
contract CreatorCoinRewardsTest is BaseTest {
|
|
19
|
+
CreatorCoin internal creatorCoin;
|
|
20
|
+
|
|
21
|
+
address internal platformReferrer;
|
|
22
|
+
address internal tradeReferrer;
|
|
23
|
+
|
|
24
|
+
function setUp() public override {
|
|
25
|
+
super.setUpWithBlockNumber(30267794);
|
|
26
|
+
|
|
27
|
+
deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
|
|
28
|
+
|
|
29
|
+
// Set up referrer addresses for all tests
|
|
30
|
+
platformReferrer = makeAddr("platformReferrer");
|
|
31
|
+
tradeReferrer = makeAddr("tradeReferrer");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function _getMultiCurvePoolConfig() internal view returns (bytes memory) {
|
|
35
|
+
int24[] memory tickLower = new int24[](1);
|
|
36
|
+
int24[] memory tickUpper = new int24[](1);
|
|
37
|
+
uint16[] memory numDiscoveryPositions = new uint16[](1);
|
|
38
|
+
uint256[] memory maxDiscoverySupplyShare = new uint256[](1);
|
|
39
|
+
|
|
40
|
+
tickLower[0] = -138_000;
|
|
41
|
+
tickUpper[0] = 81_000;
|
|
42
|
+
numDiscoveryPositions[0] = 11;
|
|
43
|
+
maxDiscoverySupplyShare[0] = 0.25e18;
|
|
44
|
+
|
|
45
|
+
return
|
|
46
|
+
abi.encode(
|
|
47
|
+
CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION,
|
|
48
|
+
address(zoraToken),
|
|
49
|
+
tickLower,
|
|
50
|
+
tickUpper,
|
|
51
|
+
numDiscoveryPositions,
|
|
52
|
+
maxDiscoverySupplyShare
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function _deployCreatorCoin(bool hasPlatformReferrer) internal {
|
|
57
|
+
bytes memory poolConfig = _getMultiCurvePoolConfig();
|
|
58
|
+
|
|
59
|
+
// Generate unique salt based on referrer addresses and block timestamp
|
|
60
|
+
bytes32 uniqueSalt = keccak256(abi.encodePacked(platformReferrer, block.timestamp, gasleft()));
|
|
61
|
+
|
|
62
|
+
vm.prank(users.creator);
|
|
63
|
+
address creatorCoinAddress = factory.deployCreatorCoin(
|
|
64
|
+
users.creator,
|
|
65
|
+
_getDefaultOwners(),
|
|
66
|
+
"https://test.com",
|
|
67
|
+
"Testcoin",
|
|
68
|
+
"TEST",
|
|
69
|
+
poolConfig,
|
|
70
|
+
hasPlatformReferrer ? platformReferrer : address(0),
|
|
71
|
+
uniqueSalt // unique salt to prevent collisions
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
creatorCoin = CreatorCoin(creatorCoinAddress);
|
|
75
|
+
vm.label(address(creatorCoin), "TEST_CREATOR_COIN");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// @dev Estimates the fees from a swap, by deploying a test hook that doesn't distribute the fees
|
|
79
|
+
/// and then reverting the state after the swap
|
|
80
|
+
function _estimateLpFees(bytes memory commands, bytes[] memory inputs) internal returns (FeeEstimatorHook.FeeEstimatorState memory feeState) {
|
|
81
|
+
uint256 snapshot = vm.snapshotState();
|
|
82
|
+
_deployFeeEstimatorHook(address(hook));
|
|
83
|
+
|
|
84
|
+
// Execute the swap
|
|
85
|
+
uint256 deadline = block.timestamp + 20;
|
|
86
|
+
router.execute(commands, inputs, deadline);
|
|
87
|
+
|
|
88
|
+
feeState = FeeEstimatorHook(payable(address(hook))).getFeeState();
|
|
89
|
+
|
|
90
|
+
vm.revertToState(snapshot);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Generic function to record token balances for all reward recipients
|
|
94
|
+
function _recordBalances(IERC20 token) internal view returns (RewardBalances memory balances) {
|
|
95
|
+
balances.creator = token.balanceOf(users.creator);
|
|
96
|
+
balances.platformReferrer = token.balanceOf(platformReferrer);
|
|
97
|
+
balances.tradeReferrer = token.balanceOf(tradeReferrer);
|
|
98
|
+
balances.protocol = token.balanceOf(creatorCoin.protocolRewardRecipient());
|
|
99
|
+
balances.doppler = token.balanceOf(creatorCoin.dopplerFeeRecipient());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Helper function to record initial ZORA token balances for all reward recipients
|
|
103
|
+
function _recordZoraBalances() internal view returns (RewardBalances memory balances) {
|
|
104
|
+
return _recordBalances(zoraToken);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Helper function to record initial creator coin balances for all reward recipients
|
|
108
|
+
function _recordCreatorCoinBalances() internal view returns (RewardBalances memory balances) {
|
|
109
|
+
return _recordBalances(IERC20(address(creatorCoin)));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Legacy function for backward compatibility
|
|
113
|
+
function _recordInitialBalances() internal view returns (RewardBalances memory balances) {
|
|
114
|
+
return _recordZoraBalances();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Helper function to calculate ZORA token reward deltas after trade
|
|
118
|
+
function _calculateZoraRewardDeltas(RewardBalances memory initialBalances) internal view returns (RewardBalances memory deltas) {
|
|
119
|
+
return
|
|
120
|
+
RewardTestHelpers.calculateTokenRewardDeltas(
|
|
121
|
+
initialBalances,
|
|
122
|
+
zoraToken,
|
|
123
|
+
users.creator,
|
|
124
|
+
platformReferrer,
|
|
125
|
+
tradeReferrer,
|
|
126
|
+
creatorCoin.protocolRewardRecipient(),
|
|
127
|
+
creatorCoin.dopplerFeeRecipient()
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Helper function to calculate creator coin reward deltas after trade
|
|
132
|
+
function _calculateCreatorCoinRewardDeltas(RewardBalances memory initialBalances) internal view returns (RewardBalances memory deltas) {
|
|
133
|
+
deltas.creator = creatorCoin.balanceOf(users.creator) - initialBalances.creator;
|
|
134
|
+
// creatorReferrer is now unified with platformReferrer
|
|
135
|
+
deltas.platformReferrer = creatorCoin.balanceOf(platformReferrer) - initialBalances.platformReferrer;
|
|
136
|
+
deltas.tradeReferrer = creatorCoin.balanceOf(tradeReferrer) - initialBalances.tradeReferrer;
|
|
137
|
+
deltas.protocol = creatorCoin.balanceOf(creatorCoin.protocolRewardRecipient()) - initialBalances.protocol;
|
|
138
|
+
deltas.doppler = creatorCoin.balanceOf(creatorCoin.dopplerFeeRecipient()) - initialBalances.doppler;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Legacy function for backward compatibility
|
|
142
|
+
function _calculateRewardDeltas(RewardBalances memory initialBalances) internal view returns (RewardBalances memory deltas) {
|
|
143
|
+
return _calculateZoraRewardDeltas(initialBalances);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Helper function to perform trades with optional trade referrer and return fee estimation
|
|
147
|
+
function _buyCreatorCoin(uint128 amountIn, bool hasTradeReferrer) internal returns (uint256 feeCurrency) {
|
|
148
|
+
deal(address(zoraToken), users.buyer, amountIn);
|
|
149
|
+
|
|
150
|
+
vm.warp(block.timestamp + 1 days);
|
|
151
|
+
|
|
152
|
+
vm.startPrank(users.buyer);
|
|
153
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), uint128(amountIn), uint48(block.timestamp + 1 days));
|
|
154
|
+
|
|
155
|
+
// Build hook data with trade referrer if provided
|
|
156
|
+
bytes memory hookData = hasTradeReferrer ? abi.encode(tradeReferrer) : bytes("");
|
|
157
|
+
|
|
158
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
159
|
+
address(zoraToken),
|
|
160
|
+
uint128(amountIn),
|
|
161
|
+
address(creatorCoin),
|
|
162
|
+
0,
|
|
163
|
+
creatorCoin.getPoolKey(),
|
|
164
|
+
hookData
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Estimate the total fees (3%) before executing
|
|
168
|
+
FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
|
|
169
|
+
|
|
170
|
+
feeCurrency = feeState.afterSwapCurrencyAmount;
|
|
171
|
+
|
|
172
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
173
|
+
vm.stopPrank();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Helper function to sell creator coin for ZORA tokens
|
|
177
|
+
function _sellCreatorCoin(uint128 amountIn, bool hasTradeReferrer) internal returns (uint256 feeCurrency) {
|
|
178
|
+
vm.startPrank(users.buyer);
|
|
179
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(creatorCoin), uint128(amountIn), uint48(block.timestamp + 1 days));
|
|
180
|
+
|
|
181
|
+
// Build hook data with trade referrer if provided
|
|
182
|
+
bytes memory hookData = hasTradeReferrer ? abi.encode(tradeReferrer) : bytes("");
|
|
183
|
+
|
|
184
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
185
|
+
address(creatorCoin),
|
|
186
|
+
uint128(amountIn),
|
|
187
|
+
address(zoraToken),
|
|
188
|
+
0,
|
|
189
|
+
creatorCoin.getPoolKey(),
|
|
190
|
+
hookData
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Estimate the fees before executing
|
|
194
|
+
FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
|
|
195
|
+
|
|
196
|
+
feeCurrency = feeState.afterSwapCurrencyAmount;
|
|
197
|
+
|
|
198
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
199
|
+
vm.stopPrank();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// @notice Test exact reward distribution percentages with platform referrer only
|
|
203
|
+
function test_rewards_platform_referrer_only() public {
|
|
204
|
+
// Deploy CreatorCoin with platform referrer
|
|
205
|
+
_deployCreatorCoin(true);
|
|
206
|
+
|
|
207
|
+
uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
|
|
208
|
+
|
|
209
|
+
// Record initial balances
|
|
210
|
+
RewardBalances memory initialBalances = _recordInitialBalances();
|
|
211
|
+
|
|
212
|
+
// Perform trade
|
|
213
|
+
uint256 zoraFees = _buyCreatorCoin(tradeAmount, false);
|
|
214
|
+
|
|
215
|
+
// Calculate reward deltas
|
|
216
|
+
RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
|
|
217
|
+
|
|
218
|
+
// Calculate market rewards from total fees
|
|
219
|
+
// Use estimated market rewards (already accounts for LP deduction and slippage)
|
|
220
|
+
RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, true, false);
|
|
221
|
+
RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// @notice Test exact reward distribution percentages with trade referrer only
|
|
225
|
+
function test_rewards_trade_referrer_only() public {
|
|
226
|
+
// Deploy CreatorCoin with no platform referrer
|
|
227
|
+
_deployCreatorCoin(false);
|
|
228
|
+
|
|
229
|
+
uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
|
|
230
|
+
|
|
231
|
+
// Record initial balances
|
|
232
|
+
RewardBalances memory initialBalances = _recordInitialBalances();
|
|
233
|
+
|
|
234
|
+
// Perform trade with trade referrer
|
|
235
|
+
uint256 zoraFees = _buyCreatorCoin(tradeAmount, true);
|
|
236
|
+
|
|
237
|
+
// Calculate reward deltas
|
|
238
|
+
RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
|
|
239
|
+
|
|
240
|
+
// Calculate market rewards from total fees
|
|
241
|
+
// Use estimated market rewards (already accounts for LP deduction and slippage)
|
|
242
|
+
RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, false, true);
|
|
243
|
+
RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// @notice Test exact reward distribution percentages with both platform and trade referrers
|
|
247
|
+
function test_rewards_both_referrers() public {
|
|
248
|
+
// Deploy CreatorCoin with platform referrer
|
|
249
|
+
_deployCreatorCoin(true);
|
|
250
|
+
|
|
251
|
+
uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
|
|
252
|
+
|
|
253
|
+
// Record initial balances
|
|
254
|
+
RewardBalances memory initialBalances = _recordInitialBalances();
|
|
255
|
+
|
|
256
|
+
// Perform trade with both referrers
|
|
257
|
+
uint256 zoraFees = _buyCreatorCoin(tradeAmount, true);
|
|
258
|
+
|
|
259
|
+
// Calculate reward deltas
|
|
260
|
+
RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
|
|
261
|
+
|
|
262
|
+
// Calculate market rewards from total fees
|
|
263
|
+
// Use estimated market rewards (already accounts for LP deduction and slippage)
|
|
264
|
+
RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, true, true);
|
|
265
|
+
RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// @notice Test exact reward distribution percentages with no referrers (baseline case)
|
|
269
|
+
function test_rewards_no_referrers() public {
|
|
270
|
+
// Deploy CreatorCoin with no platform referrer
|
|
271
|
+
_deployCreatorCoin(false);
|
|
272
|
+
|
|
273
|
+
uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
|
|
274
|
+
|
|
275
|
+
// Record initial balances
|
|
276
|
+
RewardBalances memory initialBalances = _recordInitialBalances();
|
|
277
|
+
|
|
278
|
+
// Perform trade with no trade referrer
|
|
279
|
+
uint256 zoraFees = _buyCreatorCoin(tradeAmount, false);
|
|
280
|
+
|
|
281
|
+
// Calculate reward deltas
|
|
282
|
+
RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
|
|
283
|
+
|
|
284
|
+
// Calculate market rewards from total fees
|
|
285
|
+
// Use estimated market rewards (already accounts for LP deduction and slippage)
|
|
286
|
+
RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, false, false);
|
|
287
|
+
RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// @notice Test buy-then-sell creator coin with both referrers set
|
|
291
|
+
function test_buy_then_sell_both_referrers() public {
|
|
292
|
+
uint128 buyAmount = 100 ether; // Fixed amount
|
|
293
|
+
|
|
294
|
+
// Deploy CreatorCoin with platform referrer
|
|
295
|
+
_deployCreatorCoin(true);
|
|
296
|
+
|
|
297
|
+
// Step 1: Buy creator coin (ZORA -> Creator Coin)
|
|
298
|
+
_buyCreatorCoin(buyAmount, true);
|
|
299
|
+
|
|
300
|
+
// Get buyer's creator coin balance after purchase
|
|
301
|
+
uint256 creatorCoinBalance = creatorCoin.balanceOf(users.buyer);
|
|
302
|
+
require(creatorCoinBalance > 0, "Buyer must have creator coin balance to sell");
|
|
303
|
+
|
|
304
|
+
// Record initial balances for both ZORA and creator coin
|
|
305
|
+
RewardBalances memory initialZoraBalances = _recordZoraBalances();
|
|
306
|
+
|
|
307
|
+
// Step 2: Sell creator coin (Creator Coin -> ZORA)
|
|
308
|
+
uint128 sellAmount = uint128(creatorCoinBalance);
|
|
309
|
+
uint256 sellZoraFees = _sellCreatorCoin(sellAmount, true);
|
|
310
|
+
|
|
311
|
+
// Calculate final reward deltas for both currencies
|
|
312
|
+
RewardBalances memory finalZoraRewards = _calculateZoraRewardDeltas(initialZoraBalances);
|
|
313
|
+
|
|
314
|
+
// Calculate total market rewards from both trades
|
|
315
|
+
RewardBalances memory expectedTotalRewards = RewardTestHelpers.calculateExpectedRewards(sellZoraFees, true, true);
|
|
316
|
+
|
|
317
|
+
// Validate ZORA token distributions (where all final rewards end up)
|
|
318
|
+
RewardTestHelpers.assertRewardsApproxEqRel(finalZoraRewards, expectedTotalRewards);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// @notice Test that fee estimation matches actual total reward distribution
|
|
322
|
+
function test_estimateAfterSwapCurrencyAmount() public {
|
|
323
|
+
// Deploy CreatorCoin with platform referrer
|
|
324
|
+
_deployCreatorCoin(true);
|
|
325
|
+
|
|
326
|
+
uint128 tradeAmount = 1 ether; // Much smaller amount for testing
|
|
327
|
+
|
|
328
|
+
// Build swap command but don't execute yet
|
|
329
|
+
deal(address(zoraToken), users.buyer, tradeAmount);
|
|
330
|
+
vm.startPrank(users.buyer);
|
|
331
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), tradeAmount, uint48(block.timestamp + 1 days));
|
|
332
|
+
|
|
333
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
334
|
+
address(zoraToken),
|
|
335
|
+
tradeAmount,
|
|
336
|
+
address(creatorCoin),
|
|
337
|
+
0,
|
|
338
|
+
creatorCoin.getPoolKey(),
|
|
339
|
+
bytes("") // No trade referrer
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// Estimate fees using FeeEstimatorHook
|
|
343
|
+
FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
|
|
344
|
+
|
|
345
|
+
// Record initial balances
|
|
346
|
+
RewardBalances memory initialBalances = _recordInitialBalances();
|
|
347
|
+
|
|
348
|
+
// Execute actual swap
|
|
349
|
+
router.execute(commands, inputs, block.timestamp + 20);
|
|
350
|
+
vm.stopPrank();
|
|
351
|
+
|
|
352
|
+
// Calculate total actual rewards distributed
|
|
353
|
+
RewardBalances memory finalRewards = _calculateZoraRewardDeltas(initialBalances);
|
|
354
|
+
uint256 totalActualRewards = finalRewards.creator +
|
|
355
|
+
finalRewards.platformReferrer +
|
|
356
|
+
finalRewards.tradeReferrer +
|
|
357
|
+
finalRewards.protocol +
|
|
358
|
+
finalRewards.doppler;
|
|
359
|
+
|
|
360
|
+
// Verify that total actual rewards match the estimated afterSwapCurrencyAmount
|
|
361
|
+
assertApproxEqRel(totalActualRewards, feeState.afterSwapCurrencyAmount, 0.25e18, "Total rewards should match estimated afterSwapCurrencyAmount");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function test_isLegacyCreatorCoinCategorization() public {
|
|
365
|
+
vm.createSelectFork("base", 31872861);
|
|
366
|
+
|
|
367
|
+
// Use the same creator coin from the upgrades test
|
|
368
|
+
address creatorCoinAddress = 0x2F03aB8fD97F5874bc3274C296Bb954Ae92EdA34;
|
|
369
|
+
|
|
370
|
+
// Test that the legacy creator coin is correctly categorized as a creator coin
|
|
371
|
+
bool isLegacy = CoinRewardsV4.isLegacyCreatorCoin(IHasRewardsRecipients(creatorCoinAddress));
|
|
372
|
+
|
|
373
|
+
assertTrue(isLegacy, "Legacy creator coin should be categorized as legacy creator coin");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -21,18 +21,18 @@ contract FakeHookNoInterface {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
contract
|
|
24
|
+
contract DeploymentsHooksTest is BaseTest {
|
|
25
25
|
address constant zora = 0x1111111111166b7FE7bd91427724B487980aFc69;
|
|
26
|
-
BuySupplyWithSwapRouterHook
|
|
26
|
+
BuySupplyWithSwapRouterHook buySupplyWithSwapRouterHook;
|
|
27
27
|
|
|
28
28
|
function _generateDefaultPoolConfig(address currency) internal pure returns (bytes memory) {
|
|
29
29
|
return _generatePoolConfig(currency);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function setUp() public override {
|
|
33
|
-
super.setUpWithBlockNumber(
|
|
33
|
+
super.setUpWithBlockNumber(30267794);
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
buySupplyWithSwapRouterHook = new BuySupplyWithSwapRouterHook(factory, address(swapRouter), address(V4_POOL_MANAGER));
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function _deployWithHook(address _hook, bytes memory hookData, address currency) internal returns (address, bytes memory) {
|
|
@@ -46,7 +46,7 @@ contract HooksTest is BaseTest {
|
|
|
46
46
|
"TEST",
|
|
47
47
|
poolConfig,
|
|
48
48
|
users.platformReferrer,
|
|
49
|
-
_hook,
|
|
49
|
+
address(_hook),
|
|
50
50
|
hookData
|
|
51
51
|
);
|
|
52
52
|
}
|
|
@@ -77,7 +77,7 @@ contract HooksTest is BaseTest {
|
|
|
77
77
|
users.creator,
|
|
78
78
|
ISwapRouter.ExactInputParams({
|
|
79
79
|
path: abi.encodePacked(address(weth), poolFee, USDC_ADDRESS, poolFee, zora),
|
|
80
|
-
recipient: address(
|
|
80
|
+
recipient: address(buySupplyWithSwapRouterHook),
|
|
81
81
|
amountIn: initialOrderSize,
|
|
82
82
|
amountOutMinimum: 0
|
|
83
83
|
})
|
|
@@ -94,7 +94,7 @@ contract HooksTest is BaseTest {
|
|
|
94
94
|
"TEST",
|
|
95
95
|
poolConfig,
|
|
96
96
|
users.platformReferrer,
|
|
97
|
-
address(
|
|
97
|
+
address(buySupplyWithSwapRouterHook),
|
|
98
98
|
hookData
|
|
99
99
|
);
|
|
100
100
|
|
|
@@ -121,7 +121,7 @@ contract HooksTest is BaseTest {
|
|
|
121
121
|
ISwapRouter.exactOutputSingle.selector,
|
|
122
122
|
ISwapRouter.ExactOutputParams({
|
|
123
123
|
path: abi.encodePacked(address(weth), poolFee, USDC_ADDRESS, poolFee, zora),
|
|
124
|
-
recipient: address(
|
|
124
|
+
recipient: address(buySupplyWithSwapRouterHook),
|
|
125
125
|
amountOut: 0.0001 ether,
|
|
126
126
|
amountInMaximum: 0
|
|
127
127
|
})
|
|
@@ -138,7 +138,7 @@ contract HooksTest is BaseTest {
|
|
|
138
138
|
"TEST",
|
|
139
139
|
_generateDefaultPoolConfig(zora),
|
|
140
140
|
users.platformReferrer,
|
|
141
|
-
address(
|
|
141
|
+
address(buySupplyWithSwapRouterHook),
|
|
142
142
|
hookData
|
|
143
143
|
);
|
|
144
144
|
}
|
|
@@ -170,7 +170,7 @@ contract HooksTest is BaseTest {
|
|
|
170
170
|
"TEST",
|
|
171
171
|
_generateDefaultPoolConfig(zora),
|
|
172
172
|
users.platformReferrer,
|
|
173
|
-
address(
|
|
173
|
+
address(buySupplyWithSwapRouterHook),
|
|
174
174
|
hookData
|
|
175
175
|
);
|
|
176
176
|
}
|
package/test/Factory.t.sol
CHANGED
|
@@ -13,7 +13,7 @@ contract FactoryTest is BaseTest {
|
|
|
13
13
|
|
|
14
14
|
function test_factory_constructor_and_proxy_setup() public {
|
|
15
15
|
// Impl constructor test
|
|
16
|
-
ZoraFactoryImpl impl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(
|
|
16
|
+
ZoraFactoryImpl impl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
17
17
|
assertEq(ZoraFactoryImpl(address(factory)).owner(), users.factoryOwner);
|
|
18
18
|
assertEq(ZoraFactoryImpl(address(factory)).coinV4Impl(), address(coinV4Impl));
|
|
19
19
|
|
|
@@ -38,9 +38,7 @@ contract FactoryTest is BaseTest {
|
|
|
38
38
|
|
|
39
39
|
assertEq(ZoraFactoryImpl(address(factory)).pendingOwner(), address(0));
|
|
40
40
|
|
|
41
|
-
address newFactoryImpl = address(
|
|
42
|
-
new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook))
|
|
43
|
-
);
|
|
41
|
+
address newFactoryImpl = address(new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry)));
|
|
44
42
|
|
|
45
43
|
// Upgrade to current / new impl
|
|
46
44
|
vm.prank(users.factoryOwner);
|
|
@@ -66,7 +64,7 @@ contract FactoryTest is BaseTest {
|
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
function test_upgrade() public {
|
|
69
|
-
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(
|
|
67
|
+
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
70
68
|
|
|
71
69
|
vm.prank(users.factoryOwner);
|
|
72
70
|
ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
|
|
@@ -82,12 +80,12 @@ contract FactoryTest is BaseTest {
|
|
|
82
80
|
address newImpl = address(this);
|
|
83
81
|
|
|
84
82
|
vm.prank(users.factoryOwner);
|
|
85
|
-
vm.expectRevert(
|
|
83
|
+
vm.expectRevert();
|
|
86
84
|
ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
function test_revert_invalid_owner() public {
|
|
90
|
-
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(
|
|
88
|
+
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
91
89
|
|
|
92
90
|
vm.prank(users.creator);
|
|
93
91
|
vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, users.creator));
|
|
@@ -163,6 +161,25 @@ contract FactoryTest is BaseTest {
|
|
|
163
161
|
vm.expectRevert(abi.encodeWithSelector(IZoraFactory.UpgradeToMismatchedContractName.selector, "ZoraCoinFactory", "BadFactory"));
|
|
164
162
|
ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(badImpl), "");
|
|
165
163
|
}
|
|
164
|
+
|
|
165
|
+
function test_upgrade_auto_registers_hooks() public {
|
|
166
|
+
address[] memory registeredHooks;
|
|
167
|
+
|
|
168
|
+
registeredHooks = zoraHookRegistry.getHookAddresses();
|
|
169
|
+
assertEq(registeredHooks.length, 0);
|
|
170
|
+
|
|
171
|
+
_deployHooks(); // Deploys new content and creator coin hook addresses
|
|
172
|
+
|
|
173
|
+
// Deploy new factory impl with new content and creator coin hook addresses
|
|
174
|
+
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
175
|
+
|
|
176
|
+
vm.prank(users.factoryOwner);
|
|
177
|
+
ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
|
|
178
|
+
|
|
179
|
+
registeredHooks = zoraHookRegistry.getHookAddresses();
|
|
180
|
+
assertEq(registeredHooks.length, 1);
|
|
181
|
+
assertTrue(zoraHookRegistry.isRegisteredHook(address(hook)));
|
|
182
|
+
}
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
// Mock contracts for testing
|
|
@@ -40,14 +40,14 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
40
40
|
vm.createSelectFork("base", 31653138);
|
|
41
41
|
|
|
42
42
|
address[] memory trustedMessageSenders = new address[](0);
|
|
43
|
-
(, bytes32 salt) = HooksDeployment.
|
|
43
|
+
(, bytes32 salt) = HooksDeployment.mineForCoinSalt(
|
|
44
44
|
address(this),
|
|
45
45
|
V4_POOL_MANAGER,
|
|
46
46
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
47
47
|
trustedMessageSenders,
|
|
48
48
|
hookUpgradeGate
|
|
49
49
|
);
|
|
50
|
-
IHooks hook = HooksDeployment.
|
|
50
|
+
IHooks hook = HooksDeployment.deployZoraV4CoinHook(
|
|
51
51
|
V4_POOL_MANAGER,
|
|
52
52
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
53
53
|
trustedMessageSenders,
|
|
@@ -68,7 +68,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
68
68
|
vm.createSelectFork("base", 31653138);
|
|
69
69
|
|
|
70
70
|
address[] memory trustedMessageSenders = new address[](0);
|
|
71
|
-
(, bytes32 salt) = HooksDeployment.
|
|
71
|
+
(, bytes32 salt) = HooksDeployment.mineForCoinSalt(
|
|
72
72
|
address(this),
|
|
73
73
|
V4_POOL_MANAGER,
|
|
74
74
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
@@ -77,7 +77,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
77
77
|
);
|
|
78
78
|
|
|
79
79
|
IHooks hook = HooksDeployment.deployHookWithSalt(
|
|
80
|
-
HooksDeployment.
|
|
80
|
+
HooksDeployment.makeHookCreationCode(V4_POOL_MANAGER, 0x777777751622c0d3258f214F9DF38E35BF45baF3, trustedMessageSenders, hookUpgradeGate),
|
|
81
81
|
salt
|
|
82
82
|
);
|
|
83
83
|
|
|
@@ -77,7 +77,7 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
77
77
|
address[] memory trustedMessageSenders = new address[](1);
|
|
78
78
|
trustedMessageSenders[0] = UNIVERSAL_ROUTER;
|
|
79
79
|
|
|
80
|
-
address originalHook = address(
|
|
80
|
+
address originalHook = address(hook);
|
|
81
81
|
|
|
82
82
|
address newHook = address(new LiquidityMigrationReceiver());
|
|
83
83
|
|
|
@@ -109,7 +109,7 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
109
109
|
assertEq(coinV4.balanceOf(address(originalHook)), originalHookCoinBalanceBefore, "original coin balance");
|
|
110
110
|
|
|
111
111
|
// validate that the existing hook has no liquidity for its positions
|
|
112
|
-
LpPosition[] memory positions =
|
|
112
|
+
LpPosition[] memory positions = hook.getPoolCoin(poolKey).positions;
|
|
113
113
|
|
|
114
114
|
for (uint256 i = 0; i < positions.length; i++) {
|
|
115
115
|
uint128 liquidity = V4Liquidity.getLiquidity(poolManager, address(originalHook), poolKey, positions[i].tickLower, positions[i].tickUpper);
|
|
@@ -185,8 +185,6 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
185
185
|
address currency = address(mockERC20A);
|
|
186
186
|
_deployV4Coin(currency);
|
|
187
187
|
|
|
188
|
-
address originalHook = address(contentCoinHook);
|
|
189
|
-
|
|
190
188
|
address invalidNewHook = address(new InvalidLiquidityMigrationReceiver());
|
|
191
189
|
|
|
192
190
|
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
@@ -203,14 +201,11 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
203
201
|
address currency = address(mockERC20A);
|
|
204
202
|
_deployV4Coin(currency);
|
|
205
203
|
|
|
206
|
-
address originalHook = address(
|
|
204
|
+
address originalHook = address(hook);
|
|
207
205
|
|
|
208
206
|
address newHook = address(new LiquidityMigrationReceiver());
|
|
209
207
|
|
|
210
|
-
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
211
|
-
|
|
212
208
|
// Note: NOT registering the upgrade path
|
|
213
|
-
|
|
214
209
|
// expect the migration to revert with UpgradePathNotRegistered error
|
|
215
210
|
vm.prank(users.creator);
|
|
216
211
|
vm.expectRevert(abi.encodeWithSelector(IUpgradeableV4Hook.UpgradePathNotRegistered.selector, originalHook, newHook));
|
|
@@ -221,7 +216,7 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
221
216
|
address currency = address(mockERC20A);
|
|
222
217
|
_deployV4Coin(currency);
|
|
223
218
|
|
|
224
|
-
address originalHook = address(
|
|
219
|
+
address originalHook = address(hook);
|
|
225
220
|
address newHook = address(new LiquidityMigrationReceiver());
|
|
226
221
|
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
227
222
|
|