@zoralabs/coins 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$js.log +143 -131
- package/CHANGELOG.md +20 -17
- package/abis/BaseCoin.json +5 -0
- package/abis/ContentCoin.json +5 -0
- package/abis/ICoin.json +5 -0
- package/abis/ICoinV3.json +5 -0
- package/abis/ITrendCoin.json +130 -0
- package/abis/ITrendCoinErrors.json +23 -0
- package/abis/IUniversalRouter.json +61 -0
- package/abis/IZoraFactory.json +227 -0
- package/abis/TrendCoin.json +2043 -0
- package/abis/ZoraFactoryImpl.json +232 -0
- package/dist/index.cjs +953 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +951 -138
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +1380 -149
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +960 -139
- package/package.json +2 -2
- package/src/BaseCoin.sol +12 -12
- package/src/ContentCoin.sol +20 -1
- package/src/CreatorCoin.sol +3 -0
- package/src/TrendCoin.sol +117 -0
- package/src/ZoraFactoryImpl.sol +142 -1
- package/src/hooks/ZoraV4CoinHook.sol +14 -6
- package/src/interfaces/ICoin.sol +5 -1
- package/src/interfaces/ICreatorCoin.sol +0 -3
- package/src/interfaces/IPoolManager.sol +13 -0
- package/src/interfaces/ITrendCoin.sol +26 -0
- package/src/interfaces/ITrendCoinErrors.sol +18 -0
- package/src/interfaces/IZoraFactory.sol +60 -1
- package/src/libs/CoinConstants.sol +9 -1
- package/src/libs/CoinRewardsV4.sol +67 -19
- package/src/libs/TickerUtils.sol +84 -0
- package/src/libs/UniV4SwapToCurrency.sol +2 -1
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/CreatorCoin.t.sol +2 -1
- package/test/Factory.t.sol +31 -5
- package/test/LaunchFee.t.sol +0 -2
- package/test/LiquidityMigration.t.sol +0 -2
- package/test/TrendCoin.t.sol +1077 -0
- package/test/Upgrades.t.sol +16 -3
- package/test/utils/FeeEstimatorHook.sol +33 -8
- package/test/utils/V4TestSetup.sol +36 -4
- package/wagmi.config.ts +2 -0
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {BaseTest} from "./utils/BaseTest.sol";
|
|
5
|
+
import {console} from "forge-std/console.sol";
|
|
6
|
+
|
|
7
|
+
import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
|
|
8
|
+
import {IHasRewardsRecipients} from "../src/interfaces/IHasRewardsRecipients.sol";
|
|
9
|
+
import {IHasCoinType} from "../src/interfaces/ICoin.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 {TrendCoin} from "../src/TrendCoin.sol";
|
|
17
|
+
import {ITrendCoin} from "../src/interfaces/ITrendCoin.sol";
|
|
18
|
+
import {ITrendCoinErrors} from "../src/interfaces/ITrendCoinErrors.sol";
|
|
19
|
+
import {ICoin} from "../src/interfaces/ICoin.sol";
|
|
20
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
21
|
+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
22
|
+
import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
|
|
23
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
24
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
25
|
+
import {ZoraFactoryImpl} from "../src/ZoraFactoryImpl.sol";
|
|
26
|
+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
27
|
+
import {ProxyShim} from "../src/utils/ProxyShim.sol";
|
|
28
|
+
import {ZoraFactory} from "../src/proxy/ZoraFactory.sol";
|
|
29
|
+
import {ZoraHookRegistry} from "../src/hook-registry/ZoraHookRegistry.sol";
|
|
30
|
+
import {PoolConfiguration} from "../src/types/PoolConfiguration.sol";
|
|
31
|
+
|
|
32
|
+
contract TrendCoinTest is BaseTest {
|
|
33
|
+
TrendCoin internal trendCoin;
|
|
34
|
+
|
|
35
|
+
function setUp() public override {
|
|
36
|
+
super.setUpNonForked();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============ Factory Deployment Tests ============
|
|
40
|
+
|
|
41
|
+
function test_deployTrendCoin_basic() public {
|
|
42
|
+
string memory symbol = "TESTTREND";
|
|
43
|
+
|
|
44
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
45
|
+
|
|
46
|
+
trendCoin = TrendCoin(coinAddress);
|
|
47
|
+
|
|
48
|
+
// Verify basic properties
|
|
49
|
+
assertEq(trendCoin.symbol(), symbol, "Symbol should match");
|
|
50
|
+
assertEq(trendCoin.name(), symbol, "Name should equal symbol for trend coins");
|
|
51
|
+
assertEq(uint8(trendCoin.coinType()), uint8(IHasCoinType.CoinType.Trend), "Should be Trend coin type");
|
|
52
|
+
|
|
53
|
+
// TrendCoins have no payout recipient or platform referrer
|
|
54
|
+
assertEq(trendCoin.payoutRecipient(), address(0), "Payout recipient should be zero");
|
|
55
|
+
assertEq(trendCoin.platformReferrer(), address(0), "Platform referrer should be zero");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function test_deployTrendCoin_emitsEventWithPoolConfig() public {
|
|
59
|
+
string memory symbol = "EVENTTEST";
|
|
60
|
+
|
|
61
|
+
// Get expected pool config
|
|
62
|
+
bytes memory expectedPoolConfig = CoinConfigurationVersions.defaultConfig(CoinConstants.CREATOR_COIN_CURRENCY);
|
|
63
|
+
|
|
64
|
+
vm.expectEmit(true, false, false, false);
|
|
65
|
+
emit IZoraFactory.TrendCoinCreated(
|
|
66
|
+
address(this),
|
|
67
|
+
symbol,
|
|
68
|
+
address(0),
|
|
69
|
+
PoolKey(Currency.wrap(address(0)), Currency.wrap(address(0)), 0, 0, hook),
|
|
70
|
+
bytes32(0),
|
|
71
|
+
expectedPoolConfig,
|
|
72
|
+
""
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
76
|
+
|
|
77
|
+
// Verify coin was created
|
|
78
|
+
assertTrue(coinAddress != address(0), "Coin should be created");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function test_deployTrendCoin_addressCanBePredicted() public {
|
|
82
|
+
string memory symbol = "PREDICT";
|
|
83
|
+
|
|
84
|
+
// Get predicted address before deployment
|
|
85
|
+
address predictedAddress = factory.trendCoinAddress(symbol);
|
|
86
|
+
|
|
87
|
+
// Deploy the coin
|
|
88
|
+
(address actualAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
89
|
+
|
|
90
|
+
// Verify prediction matches
|
|
91
|
+
assertEq(actualAddress, predictedAddress, "Predicted address should match actual");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function test_deployTrendCoin_tickerUniqueness() public {
|
|
95
|
+
string memory symbol = "UNIQUE";
|
|
96
|
+
|
|
97
|
+
// Deploy first coin
|
|
98
|
+
factory.deployTrendCoin(symbol, address(0), "");
|
|
99
|
+
|
|
100
|
+
// Try to deploy with same ticker - should revert
|
|
101
|
+
vm.expectRevert(abi.encodeWithSelector(ITrendCoinErrors.TickerAlreadyUsed.selector, symbol));
|
|
102
|
+
factory.deployTrendCoin(symbol, address(0), "");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function test_deployTrendCoin_tickerCaseInsensitive() public {
|
|
106
|
+
// Deploy with lowercase
|
|
107
|
+
factory.deployTrendCoin("test", address(0), "");
|
|
108
|
+
|
|
109
|
+
// Try to deploy with uppercase - should revert (same ticker, different case)
|
|
110
|
+
vm.expectRevert(abi.encodeWithSelector(ITrendCoinErrors.TickerAlreadyUsed.selector, "TEST"));
|
|
111
|
+
factory.deployTrendCoin("TEST", address(0), "");
|
|
112
|
+
|
|
113
|
+
// Try with mixed case - should also revert
|
|
114
|
+
vm.expectRevert(abi.encodeWithSelector(ITrendCoinErrors.TickerAlreadyUsed.selector, "TeSt"));
|
|
115
|
+
factory.deployTrendCoin("TeSt", address(0), "");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function test_deployTrendCoin_differentTickersAllowed() public {
|
|
119
|
+
// Deploy multiple coins with different tickers
|
|
120
|
+
(address coin1, ) = factory.deployTrendCoin("TICKER1", address(0), "");
|
|
121
|
+
(address coin2, ) = factory.deployTrendCoin("TICKER2", address(0), "");
|
|
122
|
+
(address coin3, ) = factory.deployTrendCoin("TICKER3", address(0), "");
|
|
123
|
+
|
|
124
|
+
// All should be different addresses
|
|
125
|
+
assertTrue(coin1 != coin2, "Coin1 and Coin2 should have different addresses");
|
|
126
|
+
assertTrue(coin2 != coin3, "Coin2 and Coin3 should have different addresses");
|
|
127
|
+
assertTrue(coin1 != coin3, "Coin1 and Coin3 should have different addresses");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function test_deployTrendCoin_fullSupplyToPool() public {
|
|
131
|
+
string memory symbol = "FULLPOOL";
|
|
132
|
+
|
|
133
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
134
|
+
trendCoin = TrendCoin(coinAddress);
|
|
135
|
+
|
|
136
|
+
// Total supply should be the full 1B
|
|
137
|
+
assertEq(trendCoin.totalSupply(), CoinConstants.TOTAL_SUPPLY, "Total supply should be 1B");
|
|
138
|
+
|
|
139
|
+
// Verify token allocation - the coin itself should have 0 balance (all sent to hook for pool)
|
|
140
|
+
assertEq(trendCoin.balanceOf(coinAddress), 0, "Coin should have no balance");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============ Symbol Validation Tests ============
|
|
144
|
+
|
|
145
|
+
function test_deployTrendCoin_validSymbols_lettersOnly() public {
|
|
146
|
+
// Test uppercase letters
|
|
147
|
+
(address coin1, ) = factory.deployTrendCoin("ABC", address(0), "");
|
|
148
|
+
assertTrue(coin1 != address(0), "Coin with uppercase letters should deploy");
|
|
149
|
+
|
|
150
|
+
// Test lowercase letters
|
|
151
|
+
(address coin2, ) = factory.deployTrendCoin("xyz", address(0), "");
|
|
152
|
+
assertTrue(coin2 != address(0), "Coin with lowercase letters should deploy");
|
|
153
|
+
|
|
154
|
+
// Test mixed case
|
|
155
|
+
(address coin3, ) = factory.deployTrendCoin("TestCoin", address(0), "");
|
|
156
|
+
assertTrue(coin3 != address(0), "Coin with mixed case letters should deploy");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function test_deployTrendCoin_validSymbols_numbersOnly() public {
|
|
160
|
+
(address coin1, ) = factory.deployTrendCoin("123", address(0), "");
|
|
161
|
+
assertTrue(coin1 != address(0), "Coin with numbers should deploy");
|
|
162
|
+
|
|
163
|
+
(address coin2, ) = factory.deployTrendCoin("456", address(0), "");
|
|
164
|
+
assertTrue(coin2 != address(0), "Coin with different numbers should deploy");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function test_deployTrendCoin_validSymbols_dashOnly() public {
|
|
168
|
+
(address coin1, ) = factory.deployTrendCoin("-", address(0), "");
|
|
169
|
+
assertTrue(coin1 != address(0), "Coin with single dash should deploy");
|
|
170
|
+
|
|
171
|
+
(address coin2, ) = factory.deployTrendCoin("--", address(0), "");
|
|
172
|
+
assertTrue(coin2 != address(0), "Coin with double dash should deploy");
|
|
173
|
+
|
|
174
|
+
(address coin3, ) = factory.deployTrendCoin("---", address(0), "");
|
|
175
|
+
assertTrue(coin3 != address(0), "Coin with triple dash should deploy");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function test_deployTrendCoin_validSymbols_spaceOnly() public {
|
|
179
|
+
(address coin1, ) = factory.deployTrendCoin(" ", address(0), "");
|
|
180
|
+
assertTrue(coin1 != address(0), "Coin with single space should deploy");
|
|
181
|
+
|
|
182
|
+
(address coin2, ) = factory.deployTrendCoin(" ", address(0), "");
|
|
183
|
+
assertTrue(coin2 != address(0), "Coin with double space should deploy");
|
|
184
|
+
|
|
185
|
+
(address coin3, ) = factory.deployTrendCoin(" ", address(0), "");
|
|
186
|
+
assertTrue(coin3 != address(0), "Coin with triple space should deploy");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function test_deployTrendCoin_validSymbols_allAllowedCharacters() public {
|
|
190
|
+
(address coin1, ) = factory.deployTrendCoin("ABC-123 xyz", address(0), "");
|
|
191
|
+
assertTrue(coin1 != address(0), "Coin with all character types should deploy");
|
|
192
|
+
|
|
193
|
+
(address coin2, ) = factory.deployTrendCoin("Test-123 Coin", address(0), "");
|
|
194
|
+
assertTrue(coin2 != address(0), "Coin with mixed valid characters should deploy");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
struct InvalidSymbolTestCase {
|
|
198
|
+
string symbol;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function fixtureInvalidSymbols() public pure returns (InvalidSymbolTestCase[] memory) {
|
|
202
|
+
InvalidSymbolTestCase[] memory cases = new InvalidSymbolTestCase[](34);
|
|
203
|
+
cases[0] = InvalidSymbolTestCase("TEST!");
|
|
204
|
+
cases[1] = InvalidSymbolTestCase("TEST@");
|
|
205
|
+
cases[2] = InvalidSymbolTestCase("TEST#");
|
|
206
|
+
cases[3] = InvalidSymbolTestCase("TEST$");
|
|
207
|
+
cases[4] = InvalidSymbolTestCase("TEST%");
|
|
208
|
+
cases[5] = InvalidSymbolTestCase("TEST^");
|
|
209
|
+
cases[6] = InvalidSymbolTestCase("TEST&");
|
|
210
|
+
cases[7] = InvalidSymbolTestCase("TEST*");
|
|
211
|
+
cases[8] = InvalidSymbolTestCase("TEST(");
|
|
212
|
+
cases[9] = InvalidSymbolTestCase("TEST)");
|
|
213
|
+
cases[10] = InvalidSymbolTestCase("TEST_");
|
|
214
|
+
cases[11] = InvalidSymbolTestCase("TEST+");
|
|
215
|
+
cases[12] = InvalidSymbolTestCase("TEST=");
|
|
216
|
+
cases[13] = InvalidSymbolTestCase("TEST[");
|
|
217
|
+
cases[14] = InvalidSymbolTestCase("TEST]");
|
|
218
|
+
cases[15] = InvalidSymbolTestCase("TEST{");
|
|
219
|
+
cases[16] = InvalidSymbolTestCase("TEST}");
|
|
220
|
+
cases[17] = InvalidSymbolTestCase("TEST|");
|
|
221
|
+
cases[18] = InvalidSymbolTestCase("TEST\\");
|
|
222
|
+
cases[19] = InvalidSymbolTestCase("TEST:");
|
|
223
|
+
cases[20] = InvalidSymbolTestCase("TEST;");
|
|
224
|
+
cases[21] = InvalidSymbolTestCase('TEST"');
|
|
225
|
+
cases[22] = InvalidSymbolTestCase("TEST'");
|
|
226
|
+
cases[23] = InvalidSymbolTestCase("TEST<");
|
|
227
|
+
cases[24] = InvalidSymbolTestCase("TEST>");
|
|
228
|
+
cases[25] = InvalidSymbolTestCase("TEST,");
|
|
229
|
+
cases[26] = InvalidSymbolTestCase("TEST.");
|
|
230
|
+
cases[27] = InvalidSymbolTestCase("TEST?");
|
|
231
|
+
cases[28] = InvalidSymbolTestCase("TEST/");
|
|
232
|
+
cases[29] = InvalidSymbolTestCase("TEST~");
|
|
233
|
+
cases[30] = InvalidSymbolTestCase("TEST_COIN");
|
|
234
|
+
cases[31] = InvalidSymbolTestCase("TEST.COIN");
|
|
235
|
+
cases[32] = InvalidSymbolTestCase("TEST!@#");
|
|
236
|
+
cases[33] = InvalidSymbolTestCase("");
|
|
237
|
+
return cases;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function tableInvalidSymbolsTest(InvalidSymbolTestCase memory invalidSymbols) public {
|
|
241
|
+
vm.expectRevert(ITrendCoinErrors.InvalidTickerCharacters.selector);
|
|
242
|
+
factory.deployTrendCoin(invalidSymbols.symbol, address(0), "");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function test_deployTrendCoin_validSymbols_withSpaces() public {
|
|
246
|
+
(address coin1, ) = factory.deployTrendCoin("TEST COIN", address(0), "");
|
|
247
|
+
assertTrue(coin1 != address(0), "Coin with space in middle should deploy");
|
|
248
|
+
|
|
249
|
+
(address coin2, ) = factory.deployTrendCoin(" TEST ", address(0), "");
|
|
250
|
+
assertTrue(coin2 != address(0), "Coin with leading and trailing spaces should deploy");
|
|
251
|
+
|
|
252
|
+
(address coin3, ) = factory.deployTrendCoin("TEST COIN 123", address(0), "");
|
|
253
|
+
assertTrue(coin3 != address(0), "Coin with multiple spaces should deploy");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function test_deployTrendCoin_validSymbols_lettersAndNumbers() public {
|
|
257
|
+
(address coin1, ) = factory.deployTrendCoin("TEST123", address(0), "");
|
|
258
|
+
assertTrue(coin1 != address(0), "Coin with letters then numbers should deploy");
|
|
259
|
+
|
|
260
|
+
(address coin2, ) = factory.deployTrendCoin("123TEST", address(0), "");
|
|
261
|
+
assertTrue(coin2 != address(0), "Coin with numbers then letters should deploy");
|
|
262
|
+
|
|
263
|
+
(address coin3, ) = factory.deployTrendCoin("T1E2S3T", address(0), "");
|
|
264
|
+
assertTrue(coin3 != address(0), "Coin with alternating letters and numbers should deploy");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function test_deployTrendCoin_validSymbols_lettersAndDash() public {
|
|
268
|
+
(address coin1, ) = factory.deployTrendCoin("TEST-COIN", address(0), "");
|
|
269
|
+
assertTrue(coin1 != address(0), "Coin with dash in middle should deploy");
|
|
270
|
+
|
|
271
|
+
(address coin2, ) = factory.deployTrendCoin("-TEST-", address(0), "");
|
|
272
|
+
assertTrue(coin2 != address(0), "Coin with leading and trailing dashes should deploy");
|
|
273
|
+
|
|
274
|
+
(address coin3, ) = factory.deployTrendCoin("TEST--COIN", address(0), "");
|
|
275
|
+
assertTrue(coin3 != address(0), "Coin with multiple dashes should deploy");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function test_deployTrendCoin_validSymbols_allTypes() public {
|
|
279
|
+
(address coin1, ) = factory.deployTrendCoin("TEST-123 Coin", address(0), "");
|
|
280
|
+
assertTrue(coin1 != address(0), "Coin with all character types should deploy");
|
|
281
|
+
|
|
282
|
+
(address coin2, ) = factory.deployTrendCoin("ABC 123-XYZ", address(0), "");
|
|
283
|
+
assertTrue(coin2 != address(0), "Coin with mixed valid characters should deploy");
|
|
284
|
+
|
|
285
|
+
(address coin3, ) = factory.deployTrendCoin("Test-456 Coin 789", address(0), "");
|
|
286
|
+
assertTrue(coin3 != address(0), "Coin with complex valid pattern should deploy");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ============ Fee Distribution Tests ============
|
|
290
|
+
|
|
291
|
+
function _deployTrendCoin() internal {
|
|
292
|
+
string memory symbol = "FEETEST";
|
|
293
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
294
|
+
trendCoin = TrendCoin(coinAddress);
|
|
295
|
+
vm.label(address(trendCoin), "TEST_TREND_COIN");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function _estimateLpFees(bytes memory commands, bytes[] memory inputs) internal returns (FeeEstimatorHook.FeeEstimatorState memory feeState) {
|
|
299
|
+
uint256 snapshot = vm.snapshotState();
|
|
300
|
+
_deployFeeEstimatorHook(address(hook));
|
|
301
|
+
|
|
302
|
+
// Execute the swap
|
|
303
|
+
uint256 deadline = block.timestamp + 20;
|
|
304
|
+
router.execute(commands, inputs, deadline);
|
|
305
|
+
|
|
306
|
+
feeState = FeeEstimatorHook(payable(address(hook))).getFeeState();
|
|
307
|
+
|
|
308
|
+
vm.revertToState(snapshot);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function _recordZoraBalances() internal view returns (RewardBalances memory balances) {
|
|
312
|
+
// For TrendCoins: creator and platformReferrer are address(0), so those balances will be 0
|
|
313
|
+
balances.creator = 0; // No creator for trend coins
|
|
314
|
+
balances.platformReferrer = 0; // No platform referrer for trend coins
|
|
315
|
+
balances.tradeReferrer = 0; // We'll track this if provided
|
|
316
|
+
balances.protocol = zoraToken.balanceOf(trendCoin.protocolRewardRecipient());
|
|
317
|
+
balances.doppler = zoraToken.balanceOf(trendCoin.dopplerFeeRecipient());
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function _buyTrendCoin(uint128 amountIn) internal returns (uint256 feeCurrency) {
|
|
321
|
+
deal(address(zoraToken), users.buyer, amountIn);
|
|
322
|
+
|
|
323
|
+
vm.warp(block.timestamp + 1 days);
|
|
324
|
+
|
|
325
|
+
vm.startPrank(users.buyer);
|
|
326
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), uint128(amountIn), uint48(block.timestamp + 1 days));
|
|
327
|
+
|
|
328
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
329
|
+
address(zoraToken),
|
|
330
|
+
uint128(amountIn),
|
|
331
|
+
address(trendCoin),
|
|
332
|
+
0,
|
|
333
|
+
trendCoin.getPoolKey(),
|
|
334
|
+
bytes("") // No trade referrer
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Estimate the total fees before executing
|
|
338
|
+
FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
|
|
339
|
+
|
|
340
|
+
feeCurrency = feeState.afterSwapCurrencyAmount;
|
|
341
|
+
|
|
342
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
343
|
+
vm.stopPrank();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/// @notice Test that TrendCoin fee distribution gives 80% to protocol (Zora) and 20% to LP
|
|
347
|
+
/// For TrendCoins, 100% of market rewards (80% of total fees) should go to protocol
|
|
348
|
+
function test_trendCoin_feeDistribution_80PercentToProtocol() public {
|
|
349
|
+
_deployTrendCoin();
|
|
350
|
+
|
|
351
|
+
// Roll forward past the launch fee period (10 seconds) to avoid high launch fees
|
|
352
|
+
// Launch fee starts at 99% and decays to 1% over 10 seconds
|
|
353
|
+
vm.warp(block.timestamp + 11 seconds);
|
|
354
|
+
|
|
355
|
+
uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
|
|
356
|
+
|
|
357
|
+
// Record initial protocol balance
|
|
358
|
+
address protocolRecipient = trendCoin.protocolRewardRecipient();
|
|
359
|
+
uint256 initialProtocolBalance = zoraToken.balanceOf(protocolRecipient);
|
|
360
|
+
|
|
361
|
+
// Perform trade (note: _buyTrendCoin also does vm.warp but this makes explicit we're past launch)
|
|
362
|
+
_buyTrendCoin(tradeAmount);
|
|
363
|
+
|
|
364
|
+
// Calculate final protocol balance
|
|
365
|
+
uint256 finalProtocolBalance = zoraToken.balanceOf(protocolRecipient);
|
|
366
|
+
uint256 protocolReward = finalProtocolBalance - initialProtocolBalance;
|
|
367
|
+
|
|
368
|
+
// Fee breakdown for TrendCoins after launch period:
|
|
369
|
+
// - Total fee: 1% (LP_FEE_V4 = 10,000 pips = 1%)
|
|
370
|
+
// - LP reward: 20% of fees (LP_REWARD_BPS = 2000)
|
|
371
|
+
// - Market rewards: 80% of fees
|
|
372
|
+
// - For TrendCoins: protocol gets 100% of market rewards
|
|
373
|
+
//
|
|
374
|
+
// Expected calculation:
|
|
375
|
+
// - Total fees = tradeAmount * 1% = 10 ZORA
|
|
376
|
+
// - LP gets = 10 ZORA * 20% = 2 ZORA (minted as new LP positions)
|
|
377
|
+
// - Market rewards = 10 ZORA * 80% = 8 ZORA
|
|
378
|
+
// - Protocol receives = 8 ZORA (100% of market rewards for TrendCoins)
|
|
379
|
+
uint256 expectedTotalFees = (uint256(tradeAmount) * CoinConstants.LP_FEE_V4) / 1_000_000;
|
|
380
|
+
uint256 expectedMarketRewards = (expectedTotalFees * (10_000 - CoinConstants.LP_REWARD_BPS)) / 10_000;
|
|
381
|
+
|
|
382
|
+
// Protocol should receive approximately all market rewards (allowing small rounding tolerance)
|
|
383
|
+
assertApproxEqRel(protocolReward, expectedMarketRewards, 0.01e18, "Protocol should receive ~80% of total fees");
|
|
384
|
+
|
|
385
|
+
// Verify actual value is reasonable (should be ~8 ZORA for 1000 ZORA trade)
|
|
386
|
+
assertGt(protocolReward, 7.9 ether, "Protocol reward should be > 7.9 ZORA");
|
|
387
|
+
assertLt(protocolReward, 8.1 ether, "Protocol reward should be < 8.1 ZORA");
|
|
388
|
+
|
|
389
|
+
// Verify TrendCoin recipients are correctly configured (no creator/referrer rewards)
|
|
390
|
+
assertEq(trendCoin.payoutRecipient(), address(0), "Payout recipient should be zero");
|
|
391
|
+
assertEq(trendCoin.platformReferrer(), address(0), "Platform referrer should be zero");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/// @notice Test that TrendCoin has correct coin type
|
|
395
|
+
function test_trendCoin_coinType() public {
|
|
396
|
+
_deployTrendCoin();
|
|
397
|
+
|
|
398
|
+
IHasCoinType.CoinType theCoinType = CoinRewardsV4.getCoinType(IHasRewardsRecipients(address(trendCoin)));
|
|
399
|
+
assertEq(uint8(theCoinType), uint8(IHasCoinType.CoinType.Trend), "Should be Trend coin type");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/// @notice Test that TrendCoin recipients return expected values
|
|
403
|
+
function test_trendCoin_rewardRecipients() public {
|
|
404
|
+
_deployTrendCoin();
|
|
405
|
+
|
|
406
|
+
// Payout recipient and platform referrer should be address(0)
|
|
407
|
+
assertEq(trendCoin.payoutRecipient(), address(0), "Payout recipient should be zero");
|
|
408
|
+
assertEq(trendCoin.platformReferrer(), address(0), "Platform referrer should be zero");
|
|
409
|
+
|
|
410
|
+
// Protocol reward recipient should be set
|
|
411
|
+
assertTrue(trendCoin.protocolRewardRecipient() != address(0), "Protocol recipient should be set");
|
|
412
|
+
|
|
413
|
+
// Doppler fee recipient should be set
|
|
414
|
+
assertTrue(trendCoin.dopplerFeeRecipient() != address(0), "Doppler recipient should be set");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ============ Zero Fee After Launch Tests ============
|
|
418
|
+
|
|
419
|
+
/// @notice TrendCoins should have 0 swap fee after the launch fee duration
|
|
420
|
+
/// forge-config: default.isolate = true
|
|
421
|
+
function test_trendCoin_zeroFeeAfterLaunchDuration() public {
|
|
422
|
+
_deployTrendCoin();
|
|
423
|
+
|
|
424
|
+
uint128 amountIn = 100 ether;
|
|
425
|
+
address trader = makeAddr("trader");
|
|
426
|
+
|
|
427
|
+
// Snapshot at same pool state for both swaps
|
|
428
|
+
uint256 snapshot = vm.snapshotState();
|
|
429
|
+
|
|
430
|
+
// Swap right at the end of launch period (still 1% LP fee for non-trend coins, but 0 for trend)
|
|
431
|
+
vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION);
|
|
432
|
+
deal(address(zoraToken), trader, amountIn);
|
|
433
|
+
vm.startPrank(trader);
|
|
434
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), amountIn, uint48(block.timestamp + 1 days));
|
|
435
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
436
|
+
address(zoraToken),
|
|
437
|
+
amountIn,
|
|
438
|
+
address(trendCoin),
|
|
439
|
+
0,
|
|
440
|
+
trendCoin.getPoolKey(),
|
|
441
|
+
bytes("")
|
|
442
|
+
);
|
|
443
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
444
|
+
vm.stopPrank();
|
|
445
|
+
uint256 coinsAtDuration = trendCoin.balanceOf(trader);
|
|
446
|
+
|
|
447
|
+
vm.revertToState(snapshot);
|
|
448
|
+
|
|
449
|
+
// Swap well after launch period — should yield same amount (both 0 fee)
|
|
450
|
+
vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 1 days);
|
|
451
|
+
deal(address(zoraToken), trader, amountIn);
|
|
452
|
+
vm.startPrank(trader);
|
|
453
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), amountIn, uint48(block.timestamp + 1 days));
|
|
454
|
+
(commands, inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
455
|
+
address(zoraToken),
|
|
456
|
+
amountIn,
|
|
457
|
+
address(trendCoin),
|
|
458
|
+
0,
|
|
459
|
+
trendCoin.getPoolKey(),
|
|
460
|
+
bytes("")
|
|
461
|
+
);
|
|
462
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
463
|
+
vm.stopPrank();
|
|
464
|
+
uint256 coinsAfterDuration = trendCoin.balanceOf(trader);
|
|
465
|
+
|
|
466
|
+
// Both should be approximately equal (0% fee in both cases, same pool state)
|
|
467
|
+
assertApproxEqRel(coinsAtDuration, coinsAfterDuration, 0.01e18, "both swaps should yield same coins at 0 fee");
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/// @notice TrendCoins should still have the launch fee during the launch period
|
|
471
|
+
/// forge-config: default.isolate = true
|
|
472
|
+
function test_trendCoin_launchFeeStillAppliesDuringLaunch() public {
|
|
473
|
+
_deployTrendCoin();
|
|
474
|
+
|
|
475
|
+
uint128 amountIn = 100 ether;
|
|
476
|
+
address trader = makeAddr("trader");
|
|
477
|
+
|
|
478
|
+
uint256 snapshot = vm.snapshotState();
|
|
479
|
+
|
|
480
|
+
// Swap immediately (launch fee ~99%)
|
|
481
|
+
deal(address(zoraToken), trader, amountIn);
|
|
482
|
+
vm.startPrank(trader);
|
|
483
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), amountIn, uint48(block.timestamp + 1 days));
|
|
484
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
485
|
+
address(zoraToken),
|
|
486
|
+
amountIn,
|
|
487
|
+
address(trendCoin),
|
|
488
|
+
0,
|
|
489
|
+
trendCoin.getPoolKey(),
|
|
490
|
+
bytes("")
|
|
491
|
+
);
|
|
492
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
493
|
+
vm.stopPrank();
|
|
494
|
+
uint256 coinsAtLaunch = trendCoin.balanceOf(trader);
|
|
495
|
+
|
|
496
|
+
vm.revertToState(snapshot);
|
|
497
|
+
|
|
498
|
+
// Swap after launch period (0% fee for trend coins)
|
|
499
|
+
vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 1);
|
|
500
|
+
deal(address(zoraToken), trader, amountIn);
|
|
501
|
+
vm.startPrank(trader);
|
|
502
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), amountIn, uint48(block.timestamp + 1 days));
|
|
503
|
+
(commands, inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
504
|
+
address(zoraToken),
|
|
505
|
+
amountIn,
|
|
506
|
+
address(trendCoin),
|
|
507
|
+
0,
|
|
508
|
+
trendCoin.getPoolKey(),
|
|
509
|
+
bytes("")
|
|
510
|
+
);
|
|
511
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
512
|
+
vm.stopPrank();
|
|
513
|
+
uint256 coinsPostLaunch = trendCoin.balanceOf(trader);
|
|
514
|
+
|
|
515
|
+
// Post-launch (0% fee) should yield significantly more coins than during launch (~99% fee)
|
|
516
|
+
assertGt(coinsPostLaunch, coinsAtLaunch, "should receive more coins after launch fee ends");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/// @notice Test that TrendCoin uses correct production curve configuration
|
|
520
|
+
function test_trendCoin_curveConfiguration() public {
|
|
521
|
+
// Deploy a trend coin
|
|
522
|
+
factory.deployTrendCoin("CURVETEST", address(0), "");
|
|
523
|
+
|
|
524
|
+
// Decode the production pool config
|
|
525
|
+
(
|
|
526
|
+
uint8 version,
|
|
527
|
+
address currency,
|
|
528
|
+
int24[] memory tickLower,
|
|
529
|
+
int24[] memory tickUpper,
|
|
530
|
+
uint16[] memory numDiscoveryPositions,
|
|
531
|
+
uint256[] memory maxDiscoverySupplyShare
|
|
532
|
+
) = CoinConfigurationVersions.decodeDopplerMultiCurveUniV4(CoinConstants.TREND_COIN_DEFAULT_POOL_CONFIG);
|
|
533
|
+
|
|
534
|
+
// Verify version is 4 (Doppler Multi-Curve Uni V4)
|
|
535
|
+
assertEq(version, 4, "Version should be 4");
|
|
536
|
+
|
|
537
|
+
// Verify currency is ZORA
|
|
538
|
+
assertEq(currency, CoinConstants.CREATOR_COIN_CURRENCY, "Currency should be ZORA");
|
|
539
|
+
|
|
540
|
+
// Verify 3 curves
|
|
541
|
+
assertEq(tickLower.length, 3, "Should have 3 curves");
|
|
542
|
+
assertEq(tickUpper.length, 3, "Should have 3 curves");
|
|
543
|
+
assertEq(numDiscoveryPositions.length, 3, "Should have 3 curves");
|
|
544
|
+
assertEq(maxDiscoverySupplyShare.length, 3, "Should have 3 curves");
|
|
545
|
+
|
|
546
|
+
// Verify Curve 1: ticks [-89200, -75200], 11 positions, 5% max supply
|
|
547
|
+
assertEq(tickLower[0], -89200, "Curve 1 lower tick should be -89200");
|
|
548
|
+
assertEq(tickUpper[0], -75200, "Curve 1 upper tick should be -75200");
|
|
549
|
+
assertEq(numDiscoveryPositions[0], 11, "Curve 1 should have 11 positions");
|
|
550
|
+
assertEq(maxDiscoverySupplyShare[0], 0.05e18, "Curve 1 max supply should be 5%");
|
|
551
|
+
|
|
552
|
+
// Verify Curve 2: ticks [-77200, -68200], 11 positions, 12.5% max supply
|
|
553
|
+
assertEq(tickLower[1], -77200, "Curve 2 lower tick should be -77200");
|
|
554
|
+
assertEq(tickUpper[1], -68200, "Curve 2 upper tick should be -68200");
|
|
555
|
+
assertEq(numDiscoveryPositions[1], 11, "Curve 2 should have 11 positions");
|
|
556
|
+
assertEq(maxDiscoverySupplyShare[1], 0.125e18, "Curve 2 max supply should be 12.5%");
|
|
557
|
+
|
|
558
|
+
// Verify Curve 3: ticks [-71200, -68200], 11 positions, 20% max supply
|
|
559
|
+
assertEq(tickLower[2], -71200, "Curve 3 lower tick should be -71200");
|
|
560
|
+
assertEq(tickUpper[2], -68200, "Curve 3 upper tick should be -68200");
|
|
561
|
+
assertEq(numDiscoveryPositions[2], 11, "Curve 3 should have 11 positions");
|
|
562
|
+
assertEq(maxDiscoverySupplyShare[2], 0.20e18, "Curve 3 max supply should be 20%");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// ============ Post-Deploy Hook Tests ============
|
|
566
|
+
|
|
567
|
+
function test_deployTrendCoin_withNullHook() public {
|
|
568
|
+
string memory symbol = "NULLHOOK";
|
|
569
|
+
|
|
570
|
+
// Deploy with null hook (backward compatible behavior)
|
|
571
|
+
(address coinAddress, bytes memory hookDataOut) = factory.deployTrendCoin(symbol, address(0), "");
|
|
572
|
+
|
|
573
|
+
// Verify coin was created
|
|
574
|
+
assertTrue(coinAddress != address(0), "Coin should be created");
|
|
575
|
+
assertEq(hookDataOut.length, 0, "Hook data should be empty");
|
|
576
|
+
|
|
577
|
+
// Verify TrendCoin properties
|
|
578
|
+
TrendCoin coin = TrendCoin(payable(coinAddress));
|
|
579
|
+
assertEq(coin.symbol(), symbol, "Symbol should match");
|
|
580
|
+
assertEq(uint8(coin.coinType()), uint8(IHasCoinType.CoinType.Trend), "Should be Trend coin type");
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function test_revertWhen_deployTrendCoin_ethWithoutHook() public {
|
|
584
|
+
string memory symbol = "ETHERROR";
|
|
585
|
+
|
|
586
|
+
// Should revert when sending ETH without hook
|
|
587
|
+
vm.expectRevert(IZoraFactory.EthTransferInvalid.selector);
|
|
588
|
+
factory.deployTrendCoin{value: 1 ether}(symbol, address(0), "");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function test_revertWhen_deployTrendCoin_invalidHook() public {
|
|
592
|
+
string memory symbol = "BADHOOK";
|
|
593
|
+
|
|
594
|
+
// Create invalid hook (EOA without code doesn't implement IHasAfterCoinDeploy)
|
|
595
|
+
address invalidHook = makeAddr("invalidHook");
|
|
596
|
+
|
|
597
|
+
vm.expectRevert();
|
|
598
|
+
factory.deployTrendCoin{value: 1 ether}(symbol, invalidHook, "");
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function test_revertWhen_deployTrendCoin_tickerAlreadyUsedWithHook() public {
|
|
602
|
+
string memory symbol = "DUPLICATE";
|
|
603
|
+
|
|
604
|
+
// Deploy first coin without hook
|
|
605
|
+
factory.deployTrendCoin(symbol, address(0), "");
|
|
606
|
+
|
|
607
|
+
// Try to deploy with same ticker - should revert even with hook address
|
|
608
|
+
vm.expectRevert(abi.encodeWithSelector(ITrendCoinErrors.TickerAlreadyUsed.selector, symbol));
|
|
609
|
+
factory.deployTrendCoin(symbol, makeAddr("someHook"), "");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function test_deployTrendCoin_addressPredictionWithHooks() public {
|
|
613
|
+
string memory symbol = "PREDICT2";
|
|
614
|
+
|
|
615
|
+
// Get predicted address (should work regardless of hook params)
|
|
616
|
+
address predictedAddress = factory.trendCoinAddress(symbol);
|
|
617
|
+
|
|
618
|
+
// Deploy with non-null hook address (but still null since no valid hook implementation)
|
|
619
|
+
(address actualAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
620
|
+
|
|
621
|
+
// Prediction should still match (address based on ticker only)
|
|
622
|
+
assertEq(actualAddress, predictedAddress, "Predicted address should match");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function test_deployTrendCoin_tickerUniquenessWithHooks() public {
|
|
626
|
+
// Deploy with null hook
|
|
627
|
+
factory.deployTrendCoin("test", address(0), "");
|
|
628
|
+
|
|
629
|
+
// Try to deploy with same ticker, different case - should revert
|
|
630
|
+
vm.expectRevert(abi.encodeWithSelector(ITrendCoinErrors.TickerAlreadyUsed.selector, "TEST"));
|
|
631
|
+
factory.deployTrendCoin("TEST", address(0), "");
|
|
632
|
+
|
|
633
|
+
// Try with mixed case - should also revert
|
|
634
|
+
vm.expectRevert(abi.encodeWithSelector(ITrendCoinErrors.TickerAlreadyUsed.selector, "TeSt"));
|
|
635
|
+
factory.deployTrendCoin("TeSt", address(0), "");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// ============ URI Encoding Tests ============
|
|
639
|
+
|
|
640
|
+
function test_deployTrendCoin_uriEncoding_singleSpace() public {
|
|
641
|
+
string memory symbol = "TEST COIN";
|
|
642
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
643
|
+
|
|
644
|
+
trendCoin = TrendCoin(coinAddress);
|
|
645
|
+
string memory uri = trendCoin.tokenURI();
|
|
646
|
+
|
|
647
|
+
// URI should have space converted to +
|
|
648
|
+
assertTrue(keccak256(bytes(uri)) == keccak256(bytes("https://trends.theme.wtf/trend/TEST+COIN")), "URI should have space converted to +");
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function test_deployTrendCoin_uriEncoding_multipleSpaces() public {
|
|
652
|
+
string memory symbol = "TEST COIN 123";
|
|
653
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
654
|
+
|
|
655
|
+
trendCoin = TrendCoin(coinAddress);
|
|
656
|
+
string memory uri = trendCoin.tokenURI();
|
|
657
|
+
|
|
658
|
+
// URI should have all spaces converted to +
|
|
659
|
+
assertTrue(keccak256(bytes(uri)) == keccak256(bytes("https://trends.theme.wtf/trend/TEST+COIN+123")), "URI should have all spaces converted to +");
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function test_deployTrendCoin_uriEncoding_consecutiveSpaces() public {
|
|
663
|
+
string memory symbol = "TEST COIN";
|
|
664
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
665
|
+
|
|
666
|
+
trendCoin = TrendCoin(coinAddress);
|
|
667
|
+
string memory uri = trendCoin.tokenURI();
|
|
668
|
+
|
|
669
|
+
// URI should have consecutive spaces converted to consecutive +
|
|
670
|
+
assertTrue(
|
|
671
|
+
keccak256(bytes(uri)) == keccak256(bytes("https://trends.theme.wtf/trend/TEST++COIN")),
|
|
672
|
+
"URI should have consecutive spaces converted to consecutive +"
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function test_deployTrendCoin_uriEncoding_leadingTrailingSpaces() public {
|
|
677
|
+
string memory symbol = " TEST ";
|
|
678
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
679
|
+
|
|
680
|
+
trendCoin = TrendCoin(coinAddress);
|
|
681
|
+
string memory uri = trendCoin.tokenURI();
|
|
682
|
+
|
|
683
|
+
// URI should have leading and trailing spaces converted to +
|
|
684
|
+
assertTrue(
|
|
685
|
+
keccak256(bytes(uri)) == keccak256(bytes("https://trends.theme.wtf/trend/+TEST+")),
|
|
686
|
+
"URI should have leading and trailing spaces converted to +"
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function test_deployTrendCoin_uriEncoding_noSpaces_unchanged() public {
|
|
691
|
+
string memory symbol = "TESTCOIN";
|
|
692
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
693
|
+
|
|
694
|
+
trendCoin = TrendCoin(coinAddress);
|
|
695
|
+
string memory uri = trendCoin.tokenURI();
|
|
696
|
+
|
|
697
|
+
// URI should be unchanged when no spaces
|
|
698
|
+
assertTrue(keccak256(bytes(uri)) == keccak256(bytes("https://trends.theme.wtf/trend/TESTCOIN")), "URI should be unchanged when symbol has no spaces");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function test_deployTrendCoin_uriEncoding_preservesSymbol() public {
|
|
702
|
+
string memory symbol = "TEST COIN";
|
|
703
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
704
|
+
|
|
705
|
+
trendCoin = TrendCoin(coinAddress);
|
|
706
|
+
|
|
707
|
+
// Symbol should remain unchanged (only URI is encoded)
|
|
708
|
+
assertEq(trendCoin.symbol(), symbol, "Symbol should remain unchanged");
|
|
709
|
+
assertEq(trendCoin.name(), symbol, "Name should remain unchanged");
|
|
710
|
+
|
|
711
|
+
// URI should have space converted to +
|
|
712
|
+
string memory uri = trendCoin.tokenURI();
|
|
713
|
+
assertTrue(keccak256(bytes(uri)) == keccak256(bytes("https://trends.theme.wtf/trend/TEST+COIN")), "URI should have space converted to +");
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ============ Metadata Manager Tests ============
|
|
717
|
+
|
|
718
|
+
function test_setContractURI_byMetadataManager() public {
|
|
719
|
+
_deployTrendCoin();
|
|
720
|
+
|
|
721
|
+
string memory newURI = "https://example.com/updated-metadata";
|
|
722
|
+
|
|
723
|
+
// Metadata manager should be able to update the contract URI
|
|
724
|
+
vm.prank(users.metadataManager);
|
|
725
|
+
trendCoin.setContractURI(newURI);
|
|
726
|
+
|
|
727
|
+
assertEq(trendCoin.contractURI(), newURI, "Contract URI should be updated");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function test_setContractURI_multipleUpdates() public {
|
|
731
|
+
_deployTrendCoin();
|
|
732
|
+
|
|
733
|
+
string memory uri1 = "https://example.com/v1";
|
|
734
|
+
string memory uri2 = "https://example.com/v2";
|
|
735
|
+
string memory uri3 = "https://example.com/v3";
|
|
736
|
+
|
|
737
|
+
vm.startPrank(users.metadataManager);
|
|
738
|
+
|
|
739
|
+
trendCoin.setContractURI(uri1);
|
|
740
|
+
assertEq(trendCoin.contractURI(), uri1, "Contract URI should be v1");
|
|
741
|
+
|
|
742
|
+
trendCoin.setContractURI(uri2);
|
|
743
|
+
assertEq(trendCoin.contractURI(), uri2, "Contract URI should be v2");
|
|
744
|
+
|
|
745
|
+
trendCoin.setContractURI(uri3);
|
|
746
|
+
assertEq(trendCoin.contractURI(), uri3, "Contract URI should be v3");
|
|
747
|
+
|
|
748
|
+
vm.stopPrank();
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function test_revertWhen_setContractURI_byNonMetadataManager() public {
|
|
752
|
+
_deployTrendCoin();
|
|
753
|
+
|
|
754
|
+
string memory newURI = "https://example.com/malicious";
|
|
755
|
+
|
|
756
|
+
// Random address should not be able to update the contract URI
|
|
757
|
+
address randomUser = makeAddr("randomUser");
|
|
758
|
+
vm.prank(randomUser);
|
|
759
|
+
vm.expectRevert(ITrendCoin.OnlyMetadataManager.selector);
|
|
760
|
+
trendCoin.setContractURI(newURI);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function test_revertWhen_setContractURI_byOwner() public {
|
|
764
|
+
_deployTrendCoin();
|
|
765
|
+
|
|
766
|
+
string memory newURI = "https://example.com/owner-attempt";
|
|
767
|
+
|
|
768
|
+
// Even the coin owner should not be able to use setContractURI (must use setContractURI)
|
|
769
|
+
vm.prank(users.creator);
|
|
770
|
+
vm.expectRevert(ITrendCoin.OnlyMetadataManager.selector);
|
|
771
|
+
trendCoin.setContractURI(newURI);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function test_revertWhen_setContractURI_byFactoryOwner() public {
|
|
775
|
+
_deployTrendCoin();
|
|
776
|
+
|
|
777
|
+
string memory newURI = "https://example.com/factory-owner-attempt";
|
|
778
|
+
|
|
779
|
+
// Factory owner should not be able to update the contract URI
|
|
780
|
+
vm.prank(users.factoryOwner);
|
|
781
|
+
vm.expectRevert(ITrendCoin.OnlyMetadataManager.selector);
|
|
782
|
+
trendCoin.setContractURI(newURI);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function test_setContractURI_emptyString() public {
|
|
786
|
+
_deployTrendCoin();
|
|
787
|
+
|
|
788
|
+
// Metadata manager should be able to set empty URI
|
|
789
|
+
vm.prank(users.metadataManager);
|
|
790
|
+
trendCoin.setContractURI("");
|
|
791
|
+
|
|
792
|
+
assertEq(trendCoin.contractURI(), "", "Contract URI should be empty");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ============ setNameAndSymbol Metadata Manager Tests ============
|
|
796
|
+
|
|
797
|
+
function test_setNameAndSymbol_byMetadataManager() public {
|
|
798
|
+
_deployTrendCoin();
|
|
799
|
+
|
|
800
|
+
string memory newName = "UpdatedTrend";
|
|
801
|
+
string memory newSymbol = "UPDTREND";
|
|
802
|
+
|
|
803
|
+
vm.prank(users.metadataManager);
|
|
804
|
+
trendCoin.setNameAndSymbol(newName, newSymbol);
|
|
805
|
+
|
|
806
|
+
assertEq(trendCoin.name(), newName, "Name should be updated");
|
|
807
|
+
assertEq(trendCoin.symbol(), newSymbol, "Symbol should be updated");
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function test_setNameAndSymbol_multipleUpdates() public {
|
|
811
|
+
_deployTrendCoin();
|
|
812
|
+
|
|
813
|
+
vm.startPrank(users.metadataManager);
|
|
814
|
+
|
|
815
|
+
trendCoin.setNameAndSymbol("Name1", "SYM1");
|
|
816
|
+
assertEq(trendCoin.name(), "Name1", "Name should be Name1");
|
|
817
|
+
assertEq(trendCoin.symbol(), "SYM1", "Symbol should be SYM1");
|
|
818
|
+
|
|
819
|
+
trendCoin.setNameAndSymbol("Name2", "SYM2");
|
|
820
|
+
assertEq(trendCoin.name(), "Name2", "Name should be Name2");
|
|
821
|
+
assertEq(trendCoin.symbol(), "SYM2", "Symbol should be SYM2");
|
|
822
|
+
|
|
823
|
+
vm.stopPrank();
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function test_setNameAndSymbol_emitsEvent() public {
|
|
827
|
+
_deployTrendCoin();
|
|
828
|
+
|
|
829
|
+
string memory newName = "EventTrend";
|
|
830
|
+
string memory newSymbol = "EVTTREND";
|
|
831
|
+
|
|
832
|
+
vm.prank(users.metadataManager);
|
|
833
|
+
vm.expectEmit(true, true, true, true);
|
|
834
|
+
emit ICoin.NameAndSymbolUpdated(users.metadataManager, newName, newSymbol);
|
|
835
|
+
trendCoin.setNameAndSymbol(newName, newSymbol);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function test_revertWhen_setNameAndSymbol_byNonMetadataManager() public {
|
|
839
|
+
_deployTrendCoin();
|
|
840
|
+
|
|
841
|
+
address randomUser = makeAddr("randomUser");
|
|
842
|
+
vm.prank(randomUser);
|
|
843
|
+
vm.expectRevert(ITrendCoin.OnlyMetadataManager.selector);
|
|
844
|
+
trendCoin.setNameAndSymbol("Malicious", "MAL");
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function test_revertWhen_setNameAndSymbol_byOwner() public {
|
|
848
|
+
_deployTrendCoin();
|
|
849
|
+
|
|
850
|
+
vm.prank(users.creator);
|
|
851
|
+
vm.expectRevert(ITrendCoin.OnlyMetadataManager.selector);
|
|
852
|
+
trendCoin.setNameAndSymbol("OwnerAttempt", "OWN");
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function test_revertWhen_setNameAndSymbol_byFactoryOwner() public {
|
|
856
|
+
_deployTrendCoin();
|
|
857
|
+
|
|
858
|
+
vm.prank(users.factoryOwner);
|
|
859
|
+
vm.expectRevert(ITrendCoin.OnlyMetadataManager.selector);
|
|
860
|
+
trendCoin.setNameAndSymbol("FactoryAttempt", "FAC");
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function test_revertWhen_setNameAndSymbol_emptyName() public {
|
|
864
|
+
_deployTrendCoin();
|
|
865
|
+
|
|
866
|
+
vm.prank(users.metadataManager);
|
|
867
|
+
vm.expectRevert(abi.encodeWithSelector(ICoin.NameIsRequired.selector));
|
|
868
|
+
trendCoin.setNameAndSymbol("", "SYM");
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// ============ Pool Config Admin Tests ============
|
|
872
|
+
|
|
873
|
+
function _getDefaultPoolConfigParams()
|
|
874
|
+
internal
|
|
875
|
+
pure
|
|
876
|
+
returns (
|
|
877
|
+
address currency,
|
|
878
|
+
int24[] memory tickLower,
|
|
879
|
+
int24[] memory tickUpper,
|
|
880
|
+
uint16[] memory numDiscoveryPositions,
|
|
881
|
+
uint256[] memory maxDiscoverySupplyShare
|
|
882
|
+
)
|
|
883
|
+
{
|
|
884
|
+
(, currency, tickLower, tickUpper, numDiscoveryPositions, maxDiscoverySupplyShare) = CoinConfigurationVersions.decodeDopplerMultiCurveUniV4(
|
|
885
|
+
CoinConstants.TREND_COIN_DEFAULT_POOL_CONFIG
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function test_setTrendCoinPoolConfig_ownerCanSet() public {
|
|
890
|
+
bytes memory expectedPoolConfig = CoinConstants.TREND_COIN_DEFAULT_POOL_CONFIG;
|
|
891
|
+
|
|
892
|
+
// Get the factory owner
|
|
893
|
+
address factoryOwner = ZoraFactoryImpl(address(factory)).owner();
|
|
894
|
+
|
|
895
|
+
(
|
|
896
|
+
address currency,
|
|
897
|
+
int24[] memory tickLower,
|
|
898
|
+
int24[] memory tickUpper,
|
|
899
|
+
uint16[] memory numDiscoveryPositions,
|
|
900
|
+
uint256[] memory maxDiscoverySupplyShare
|
|
901
|
+
) = _getDefaultPoolConfigParams();
|
|
902
|
+
|
|
903
|
+
vm.prank(factoryOwner);
|
|
904
|
+
factory.setTrendCoinPoolConfig(currency, tickLower, tickUpper, numDiscoveryPositions, maxDiscoverySupplyShare);
|
|
905
|
+
|
|
906
|
+
// Verify it was set
|
|
907
|
+
bytes memory storedConfig = factory.trendCoinPoolConfig();
|
|
908
|
+
assertEq(keccak256(storedConfig), keccak256(expectedPoolConfig), "Pool config should be stored");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function test_setTrendCoinPoolConfig_emitsEvent() public {
|
|
912
|
+
bytes memory expectedPoolConfig = CoinConstants.TREND_COIN_DEFAULT_POOL_CONFIG;
|
|
913
|
+
address factoryOwner = ZoraFactoryImpl(address(factory)).owner();
|
|
914
|
+
|
|
915
|
+
(
|
|
916
|
+
address currency,
|
|
917
|
+
int24[] memory tickLower,
|
|
918
|
+
int24[] memory tickUpper,
|
|
919
|
+
uint16[] memory numDiscoveryPositions,
|
|
920
|
+
uint256[] memory maxDiscoverySupplyShare
|
|
921
|
+
) = _getDefaultPoolConfigParams();
|
|
922
|
+
|
|
923
|
+
vm.expectEmit(false, false, false, true);
|
|
924
|
+
emit IZoraFactory.TrendCoinPoolConfigUpdated(expectedPoolConfig);
|
|
925
|
+
|
|
926
|
+
vm.prank(factoryOwner);
|
|
927
|
+
factory.setTrendCoinPoolConfig(currency, tickLower, tickUpper, numDiscoveryPositions, maxDiscoverySupplyShare);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function test_revertWhen_setTrendCoinPoolConfig_nonOwner() public {
|
|
931
|
+
address nonOwner = makeAddr("nonOwner");
|
|
932
|
+
|
|
933
|
+
(
|
|
934
|
+
address currency,
|
|
935
|
+
int24[] memory tickLower,
|
|
936
|
+
int24[] memory tickUpper,
|
|
937
|
+
uint16[] memory numDiscoveryPositions,
|
|
938
|
+
uint256[] memory maxDiscoverySupplyShare
|
|
939
|
+
) = _getDefaultPoolConfigParams();
|
|
940
|
+
|
|
941
|
+
vm.prank(nonOwner);
|
|
942
|
+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, nonOwner));
|
|
943
|
+
factory.setTrendCoinPoolConfig(currency, tickLower, tickUpper, numDiscoveryPositions, maxDiscoverySupplyShare);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function test_revertWhen_setTrendCoinPoolConfig_emptyArrays() public {
|
|
947
|
+
address factoryOwner = ZoraFactoryImpl(address(factory)).owner();
|
|
948
|
+
address currency = CoinConstants.CREATOR_COIN_CURRENCY;
|
|
949
|
+
|
|
950
|
+
int24[] memory tickLower = new int24[](0);
|
|
951
|
+
int24[] memory tickUpper = new int24[](0);
|
|
952
|
+
uint16[] memory numDiscoveryPositions = new uint16[](0);
|
|
953
|
+
uint256[] memory maxDiscoverySupplyShare = new uint256[](0);
|
|
954
|
+
|
|
955
|
+
vm.prank(factoryOwner);
|
|
956
|
+
vm.expectRevert(IZoraFactory.InvalidConfig.selector);
|
|
957
|
+
factory.setTrendCoinPoolConfig(currency, tickLower, tickUpper, numDiscoveryPositions, maxDiscoverySupplyShare);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function test_revertWhen_setTrendCoinPoolConfig_mismatchedArrayLengths() public {
|
|
961
|
+
address factoryOwner = ZoraFactoryImpl(address(factory)).owner();
|
|
962
|
+
address currency = CoinConstants.CREATOR_COIN_CURRENCY;
|
|
963
|
+
|
|
964
|
+
int24[] memory tickLower = new int24[](2);
|
|
965
|
+
int24[] memory tickUpper = new int24[](3); // Mismatched length
|
|
966
|
+
uint16[] memory numDiscoveryPositions = new uint16[](2);
|
|
967
|
+
uint256[] memory maxDiscoverySupplyShare = new uint256[](2);
|
|
968
|
+
|
|
969
|
+
vm.prank(factoryOwner);
|
|
970
|
+
vm.expectRevert(IZoraFactory.InvalidConfig.selector);
|
|
971
|
+
factory.setTrendCoinPoolConfig(currency, tickLower, tickUpper, numDiscoveryPositions, maxDiscoverySupplyShare);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function test_revertWhen_deployTrendCoin_configNotSet() public {
|
|
975
|
+
// Deploy a fresh factory without pool config set
|
|
976
|
+
// We need to test this with a fresh factory that hasn't had the config set
|
|
977
|
+
// Since our test setup sets the config, we need to use a different approach
|
|
978
|
+
|
|
979
|
+
// Deploy a new factory proxy
|
|
980
|
+
address proxyShim = address(new ProxyShim());
|
|
981
|
+
ZoraFactory newFactoryProxy = new ZoraFactory(proxyShim);
|
|
982
|
+
|
|
983
|
+
// Create a new hook registry that includes the new factory as an owner
|
|
984
|
+
ZoraHookRegistry newHookRegistry = new ZoraHookRegistry();
|
|
985
|
+
address[] memory initialOwners = new address[](2);
|
|
986
|
+
initialOwners[0] = address(this);
|
|
987
|
+
initialOwners[1] = address(newFactoryProxy);
|
|
988
|
+
newHookRegistry.initialize(initialOwners);
|
|
989
|
+
|
|
990
|
+
// Create a new factory impl with the new hook registry
|
|
991
|
+
ZoraFactoryImpl newFactoryImpl = new ZoraFactoryImpl(
|
|
992
|
+
address(coinV4Impl),
|
|
993
|
+
address(creatorCoinImpl),
|
|
994
|
+
address(trendCoinImpl),
|
|
995
|
+
address(hook),
|
|
996
|
+
address(newHookRegistry)
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
// Upgrade to real impl and initialize
|
|
1000
|
+
UUPSUpgradeable(address(newFactoryProxy)).upgradeToAndCall(
|
|
1001
|
+
address(newFactoryImpl),
|
|
1002
|
+
abi.encodeWithSelector(ZoraFactoryImpl.initialize.selector, address(this))
|
|
1003
|
+
);
|
|
1004
|
+
|
|
1005
|
+
IZoraFactory newFactory = IZoraFactory(address(newFactoryProxy));
|
|
1006
|
+
|
|
1007
|
+
// Try to deploy trend coin without setting config first
|
|
1008
|
+
vm.expectRevert(IZoraFactory.TrendCoinPoolConfigNotSet.selector);
|
|
1009
|
+
newFactory.deployTrendCoin("NOCONFIG", address(0), "");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function test_deployTrendCoin_usesStoredConfig() public {
|
|
1013
|
+
// The factory should already have config set from setUp
|
|
1014
|
+
// Deploy a trend coin and verify it works
|
|
1015
|
+
string memory symbol = "CONFIGTEST";
|
|
1016
|
+
|
|
1017
|
+
(address coinAddress, ) = factory.deployTrendCoin(symbol, address(0), "");
|
|
1018
|
+
|
|
1019
|
+
// Verify the coin was created successfully
|
|
1020
|
+
trendCoin = TrendCoin(coinAddress);
|
|
1021
|
+
assertEq(trendCoin.symbol(), symbol, "Symbol should match");
|
|
1022
|
+
assertEq(uint8(trendCoin.coinType()), uint8(IHasCoinType.CoinType.Trend), "Should be Trend coin type");
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function test_trendCoinPoolConfig_returnsStoredConfig() public {
|
|
1026
|
+
bytes memory expectedConfig = CoinConstants.TREND_COIN_DEFAULT_POOL_CONFIG;
|
|
1027
|
+
|
|
1028
|
+
// Get stored config (should be set from setUp)
|
|
1029
|
+
bytes memory storedConfig = factory.trendCoinPoolConfig();
|
|
1030
|
+
|
|
1031
|
+
assertEq(keccak256(storedConfig), keccak256(expectedConfig), "Should return the stored config");
|
|
1032
|
+
}
|
|
1033
|
+
// ============ Reinitialization Protection Tests ============
|
|
1034
|
+
|
|
1035
|
+
function test_revertWhen_reinitializeTrendCoin() public {
|
|
1036
|
+
_deployTrendCoin();
|
|
1037
|
+
|
|
1038
|
+
// Get pool key and configuration from the deployed coin
|
|
1039
|
+
ICoin coin = ICoin(address(trendCoin));
|
|
1040
|
+
PoolKey memory poolKey_ = coin.getPoolKey();
|
|
1041
|
+
PoolConfiguration memory poolConfig_ = coin.getPoolConfiguration();
|
|
1042
|
+
|
|
1043
|
+
// Try to reinitialize via initializeTrendCoin - should fail
|
|
1044
|
+
address[] memory owners = new address[](1);
|
|
1045
|
+
owners[0] = users.creator;
|
|
1046
|
+
|
|
1047
|
+
vm.expectRevert(Initializable.InvalidInitialization.selector);
|
|
1048
|
+
trendCoin.initializeTrendCoin(owners, "REINIT", poolKey_, uint160(1 << 96), poolConfig_);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function test_revertWhen_legacyInitialize() public {
|
|
1052
|
+
_deployTrendCoin();
|
|
1053
|
+
|
|
1054
|
+
// Get pool key and configuration from the deployed coin
|
|
1055
|
+
ICoin coin = ICoin(address(trendCoin));
|
|
1056
|
+
PoolKey memory poolKey_ = coin.getPoolKey();
|
|
1057
|
+
PoolConfiguration memory poolConfig_ = coin.getPoolConfiguration();
|
|
1058
|
+
|
|
1059
|
+
// Try to call legacy initialize - should always revert with UseSpecificTrendCoinInitialize
|
|
1060
|
+
address[] memory owners = new address[](1);
|
|
1061
|
+
owners[0] = users.creator;
|
|
1062
|
+
|
|
1063
|
+
vm.expectRevert(ITrendCoinErrors.UseSpecificTrendCoinInitialize.selector);
|
|
1064
|
+
trendCoin.initialize(
|
|
1065
|
+
address(0),
|
|
1066
|
+
owners,
|
|
1067
|
+
"https://example.com/reinit",
|
|
1068
|
+
"REINIT",
|
|
1069
|
+
"REINIT",
|
|
1070
|
+
address(0),
|
|
1071
|
+
CoinConstants.CREATOR_COIN_CURRENCY,
|
|
1072
|
+
poolKey_,
|
|
1073
|
+
uint160(1 << 96),
|
|
1074
|
+
poolConfig_
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
}
|