@zoralabs/coins 0.6.1 → 0.7.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.log +69 -55
- package/CHANGELOG.md +12 -0
- package/abis/BaseTest.json +0 -23
- package/abis/Coin.json +186 -77
- package/abis/CoinConfigurationVersions.json +7 -0
- package/abis/CoinSetup.json +7 -0
- package/abis/CoinTest.json +5 -49
- package/abis/CustomRevert.json +28 -0
- package/abis/DopplerUniswapV3Test.json +891 -0
- package/abis/FactoryTest.json +7 -23
- package/abis/IAirlock.json +15 -0
- package/abis/ICoin.json +52 -34
- package/abis/IDopplerErrors.json +44 -0
- package/abis/INonfungiblePositionManager.json +13 -0
- package/abis/IUniswapV3Factory.json +198 -0
- package/abis/IUniswapV3Pool.json +135 -0
- package/abis/MultiOwnableTest.json +0 -23
- package/abis/SafeCast.json +7 -0
- package/abis/Simulate.json +120 -0
- package/abis/SqrtPriceMath.json +22 -0
- package/abis/TickMath.json +24 -0
- package/abis/ZoraFactoryImpl.json +59 -0
- package/addresses/8453.json +3 -3
- package/dist/index.cjs +160 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +160 -39
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +349 -67
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +161 -40
- package/package.json +3 -3
- package/script/CoinsDeployerBase.sol +1 -1
- package/script/Simulate.s.sol +67 -0
- package/src/Coin.sol +159 -90
- package/src/ZoraFactoryImpl.sol +47 -1
- package/src/interfaces/IAirlock.sol +6 -0
- package/src/interfaces/ICoin.sol +18 -2
- package/src/interfaces/IDopplerErrors.sol +14 -0
- package/src/interfaces/INonfungiblePositionManager.sol +2 -0
- package/src/interfaces/IUniswapV3Factory.sol +64 -0
- package/src/interfaces/IUniswapV3Pool.sol +48 -0
- package/src/libs/CoinConfigurationVersions.sol +9 -0
- package/src/libs/CoinDopplerUniV3.sol +202 -0
- package/src/libs/CoinLegacy.sol +48 -0
- package/src/libs/CoinSetup.sol +37 -0
- package/src/libs/MarketConstants.sol +25 -0
- package/src/types/LpPosition.sol +8 -0
- package/src/types/PoolState.sol +24 -0
- package/src/utils/CoinConstants.sol +5 -12
- package/src/utils/uniswap/BitMath.sol +55 -0
- package/src/utils/uniswap/CustomRevert.sol +111 -0
- package/src/utils/uniswap/FixedPoint96.sol +11 -0
- package/src/utils/uniswap/FullMath.sol +118 -0
- package/src/utils/uniswap/LiquidityAmounts.sol +117 -0
- package/src/utils/uniswap/SafeCast.sol +61 -0
- package/src/utils/uniswap/SqrtPriceMath.sol +249 -0
- package/src/utils/uniswap/TickMath.sol +244 -0
- package/src/utils/uniswap/UnsafeMath.sol +30 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Coin.t.sol +65 -65
- package/test/CoinDopplerUniV3.t.sol +452 -0
- package/test/Factory.t.sol +49 -7
- package/test/utils/BaseTest.sol +26 -7
- package/wagmi.config.ts +1 -1
- package/abis/IERC721Receiver.json +0 -36
- package/src/utils/TickMath.sol +0 -210
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
|
|
5
|
+
import {MarketConstants} from "../src/libs/MarketConstants.sol";
|
|
6
|
+
import {BaseTest} from "./utils/BaseTest.sol";
|
|
7
|
+
import {Coin} from "../src/Coin.sol";
|
|
8
|
+
import {IUniswapV3Pool} from "../src/interfaces/IUniswapV3Pool.sol";
|
|
9
|
+
import {LpPosition} from "../src/types/LpPosition.sol";
|
|
10
|
+
import {IDopplerErrors} from "../src/interfaces/IDopplerErrors.sol";
|
|
11
|
+
import {CoinDopplerUniV3} from "../src/libs/CoinDopplerUniV3.sol";
|
|
12
|
+
import {TickMath} from "../src/utils/uniswap/TickMath.sol";
|
|
13
|
+
|
|
14
|
+
contract DopplerUniswapV3Test is BaseTest {
|
|
15
|
+
int24 internal constant DEFAULT_DISCOVERY_TICK_LOWER = -777000;
|
|
16
|
+
int24 internal constant DEFAULT_DISCOVERY_TICK_UPPER = 222000;
|
|
17
|
+
uint16 internal constant DEFAULT_NUM_DISCOVERY_POSITIONS = 10; // will be 11 total with tail position
|
|
18
|
+
uint256 internal constant DEFAULT_DISCOVERY_SUPPLY_SHARE = 0.495e18; // half of the 990m total pool supply
|
|
19
|
+
|
|
20
|
+
function _generatePoolConfig(
|
|
21
|
+
uint8 version_,
|
|
22
|
+
address currency_,
|
|
23
|
+
int24 tickLower_,
|
|
24
|
+
int24 tickUpper_,
|
|
25
|
+
uint16 numDiscoveryPositions_,
|
|
26
|
+
uint256 maxDiscoverySupplyShare_
|
|
27
|
+
) internal pure returns (bytes memory) {
|
|
28
|
+
return abi.encode(version_, currency_, tickLower_, tickUpper_, numDiscoveryPositions_, maxDiscoverySupplyShare_);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _deployCoin(bytes memory poolConfig_) internal {
|
|
32
|
+
vm.prank(users.creator);
|
|
33
|
+
(address coinAddress, ) = factory.deploy(
|
|
34
|
+
users.creator,
|
|
35
|
+
_getDefaultOwners(),
|
|
36
|
+
"https://test.com",
|
|
37
|
+
"Testcoin",
|
|
38
|
+
"TEST",
|
|
39
|
+
poolConfig_,
|
|
40
|
+
users.platformReferrer,
|
|
41
|
+
0
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
coin = Coin(payable(coinAddress));
|
|
45
|
+
pool = IUniswapV3Pool(coin.poolAddress());
|
|
46
|
+
|
|
47
|
+
vm.label(address(coin), "COIN");
|
|
48
|
+
vm.label(address(pool), "POOL");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function setUp() public override {
|
|
52
|
+
super.setUp();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function test_deploy_legacy_eth_config() public {
|
|
56
|
+
bytes memory poolConfig = _generatePoolConfig(
|
|
57
|
+
CoinConfigurationVersions.LEGACY_POOL_VERSION,
|
|
58
|
+
address(weth),
|
|
59
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
60
|
+
0,
|
|
61
|
+
1,
|
|
62
|
+
0
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
_deployCoin(poolConfig);
|
|
66
|
+
|
|
67
|
+
assertEq(coin.currency(), address(weth), "currency");
|
|
68
|
+
assertEq(coin.totalSupply(), 1_000_000_000e18, "totalSupply");
|
|
69
|
+
assertEq(coin.balanceOf(users.creator), 10_000_000e18, "balanceOf creator");
|
|
70
|
+
assertGt(coin.balanceOf(coin.poolAddress()), 989_999_999e18, "balanceOf pool");
|
|
71
|
+
|
|
72
|
+
(
|
|
73
|
+
address asset,
|
|
74
|
+
address numeraire,
|
|
75
|
+
int24 tickLower,
|
|
76
|
+
int24 tickUpper,
|
|
77
|
+
uint16 numPositions,
|
|
78
|
+
bool isInitialized,
|
|
79
|
+
bool isExited,
|
|
80
|
+
uint256 maxShareToBeSold,
|
|
81
|
+
uint256 totalTokensOnBondingCurve
|
|
82
|
+
) = coin.poolState();
|
|
83
|
+
|
|
84
|
+
assertEq(asset, address(coin));
|
|
85
|
+
assertEq(numeraire, address(weth));
|
|
86
|
+
assertEq(numPositions, 1);
|
|
87
|
+
assertTrue(isInitialized);
|
|
88
|
+
assertFalse(isExited);
|
|
89
|
+
assertEq(maxShareToBeSold, 0);
|
|
90
|
+
assertEq(totalTokensOnBondingCurve, POOL_LAUNCH_SUPPLY);
|
|
91
|
+
|
|
92
|
+
bool isCoinToken0 = address(coin) < address(weth);
|
|
93
|
+
|
|
94
|
+
if (isCoinToken0) {
|
|
95
|
+
assertEq(tickLower, MarketConstants.LP_TICK_LOWER_WETH);
|
|
96
|
+
assertEq(tickUpper, MarketConstants.LP_TICK_UPPER);
|
|
97
|
+
} else {
|
|
98
|
+
assertEq(tickLower, -MarketConstants.LP_TICK_UPPER);
|
|
99
|
+
assertEq(tickUpper, -MarketConstants.LP_TICK_LOWER_WETH);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function test_invalid_tick_range() public {
|
|
104
|
+
bytes memory poolConfig = _generatePoolConfig(CoinConfigurationVersions.LEGACY_POOL_VERSION, address(weth), -100, 100, 100, 0.5e18);
|
|
105
|
+
|
|
106
|
+
vm.expectRevert();
|
|
107
|
+
factory.deploy(users.creator, _getDefaultOwners(), "https://test.com", "Testcoin", "TEST", poolConfig, users.platformReferrer, 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function test_inverted_tick_range_revert() public {
|
|
111
|
+
// These tick ranges are flipped
|
|
112
|
+
bytes memory poolConfig = _generatePoolConfig(CoinConfigurationVersions.LEGACY_POOL_VERSION, address(weth), 10000, -10000, 100, 0.5e18);
|
|
113
|
+
|
|
114
|
+
vm.expectRevert(abi.encodeWithSignature("InvalidWethLowerTick()"));
|
|
115
|
+
factory.deploy(users.creator, _getDefaultOwners(), "https://test.com", "Testcoin", "TEST", poolConfig, users.platformReferrer, 0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function test_deploy_legacy_eth_config_with_prebuy(uint256 initialOrderSize) public {
|
|
119
|
+
vm.assume(initialOrderSize > MIN_ORDER_SIZE);
|
|
120
|
+
vm.assume(initialOrderSize < 10 ether);
|
|
121
|
+
|
|
122
|
+
vm.deal(users.creator, initialOrderSize);
|
|
123
|
+
|
|
124
|
+
bytes memory poolConfig = _generatePoolConfig(
|
|
125
|
+
CoinConfigurationVersions.LEGACY_POOL_VERSION,
|
|
126
|
+
address(weth),
|
|
127
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
128
|
+
0,
|
|
129
|
+
0,
|
|
130
|
+
0
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
vm.prank(users.creator);
|
|
134
|
+
(address coinAddress, ) = factory.deploy{value: initialOrderSize}(
|
|
135
|
+
users.creator,
|
|
136
|
+
_getDefaultOwners(),
|
|
137
|
+
"https://test.com",
|
|
138
|
+
"Testcoin",
|
|
139
|
+
"TEST",
|
|
140
|
+
poolConfig,
|
|
141
|
+
users.platformReferrer,
|
|
142
|
+
initialOrderSize
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
coin = Coin(payable(coinAddress));
|
|
146
|
+
pool = IUniswapV3Pool(coin.poolAddress());
|
|
147
|
+
|
|
148
|
+
assertGt(coin.balanceOf(users.creator), 10_000_000e18, "balanceOf creator");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function test_deploy_legacy_usdc_config_with_prebuy() public {
|
|
152
|
+
address[] memory owners = new address[](1);
|
|
153
|
+
owners[0] = users.creator;
|
|
154
|
+
|
|
155
|
+
uint256 orderSize = dealUSDC(users.creator, 100);
|
|
156
|
+
|
|
157
|
+
vm.prank(users.creator);
|
|
158
|
+
usdc.approve(address(factory), orderSize);
|
|
159
|
+
|
|
160
|
+
bytes memory poolConfig = _generatePoolConfig(CoinConfigurationVersions.LEGACY_POOL_VERSION, address(usdc), USDC_TICK_LOWER, 0, 0, 0);
|
|
161
|
+
|
|
162
|
+
vm.prank(users.creator);
|
|
163
|
+
(address coinAddress, uint256 coinsPurchased) = factory.deploy(
|
|
164
|
+
users.creator,
|
|
165
|
+
owners,
|
|
166
|
+
"https://testcoinusdcpair.com",
|
|
167
|
+
"Testcoinusdcpair",
|
|
168
|
+
"TESTCOINUSDCPAIR",
|
|
169
|
+
poolConfig,
|
|
170
|
+
users.platformReferrer,
|
|
171
|
+
orderSize
|
|
172
|
+
);
|
|
173
|
+
coin = Coin(payable(coinAddress));
|
|
174
|
+
pool = IUniswapV3Pool(coin.poolAddress());
|
|
175
|
+
vm.label(address(coin), "COIN");
|
|
176
|
+
vm.label(address(pool), "POOL");
|
|
177
|
+
|
|
178
|
+
assertEq(coin.currency(), address(usdc), "currency");
|
|
179
|
+
assertEq(coin.balanceOf(users.creator), CREATOR_LAUNCH_REWARD + coinsPurchased);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function test_deploy_doppler_eth() public {
|
|
183
|
+
bytes memory poolConfig = _generatePoolConfig(
|
|
184
|
+
CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
|
|
185
|
+
address(weth),
|
|
186
|
+
DEFAULT_DISCOVERY_TICK_LOWER,
|
|
187
|
+
DEFAULT_DISCOVERY_TICK_UPPER,
|
|
188
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
189
|
+
DEFAULT_DISCOVERY_SUPPLY_SHARE
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
_deployCoin(poolConfig);
|
|
193
|
+
|
|
194
|
+
(
|
|
195
|
+
address asset,
|
|
196
|
+
address numeraire,
|
|
197
|
+
,
|
|
198
|
+
,
|
|
199
|
+
uint16 numPositions,
|
|
200
|
+
bool isInitialized,
|
|
201
|
+
bool isExited,
|
|
202
|
+
uint256 maxShareToBeSold,
|
|
203
|
+
uint256 totalTokensOnBondingCurve
|
|
204
|
+
) = coin.poolState();
|
|
205
|
+
|
|
206
|
+
assertEq(asset, address(coin), "poolState.asset");
|
|
207
|
+
assertEq(numeraire, address(weth), "poolState.numeraire");
|
|
208
|
+
assertEq(numPositions, DEFAULT_NUM_DISCOVERY_POSITIONS, "poolState.numPositions");
|
|
209
|
+
assertTrue(isInitialized, "poolState.isInitialized");
|
|
210
|
+
assertFalse(isExited, "poolState.isExited");
|
|
211
|
+
assertEq(maxShareToBeSold, DEFAULT_DISCOVERY_SUPPLY_SHARE, "poolState.maxShareToBeSold");
|
|
212
|
+
assertEq(totalTokensOnBondingCurve, POOL_LAUNCH_SUPPLY, "poolState.totalTokensOnBondingCurve");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function test_deploy_doppler_eth_with_prebuy(uint256 initialOrderSize) public {
|
|
216
|
+
vm.assume(initialOrderSize > MIN_ORDER_SIZE);
|
|
217
|
+
vm.assume(initialOrderSize < 1 ether);
|
|
218
|
+
|
|
219
|
+
vm.deal(users.creator, initialOrderSize);
|
|
220
|
+
|
|
221
|
+
bytes memory poolConfig = _generatePoolConfig(
|
|
222
|
+
CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
|
|
223
|
+
address(weth),
|
|
224
|
+
DEFAULT_DISCOVERY_TICK_LOWER,
|
|
225
|
+
DEFAULT_DISCOVERY_TICK_UPPER,
|
|
226
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
227
|
+
DEFAULT_DISCOVERY_SUPPLY_SHARE
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
vm.prank(users.creator);
|
|
231
|
+
(address coinAddress, uint256 coinsPurchased) = factory.deploy{value: initialOrderSize}(
|
|
232
|
+
users.creator,
|
|
233
|
+
_getDefaultOwners(),
|
|
234
|
+
"https://test.com",
|
|
235
|
+
"Testcoin",
|
|
236
|
+
"TEST",
|
|
237
|
+
poolConfig,
|
|
238
|
+
users.platformReferrer,
|
|
239
|
+
initialOrderSize
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
coin = Coin(payable(coinAddress));
|
|
243
|
+
pool = IUniswapV3Pool(coin.poolAddress());
|
|
244
|
+
|
|
245
|
+
assertEq(coin.currency(), address(weth), "currency");
|
|
246
|
+
assertGt(coinsPurchased, 0, "coinsPurchased > 0");
|
|
247
|
+
assertEq(coin.balanceOf(users.creator), CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
|
|
248
|
+
assertGt(weth.balanceOf(address(pool)), 0, "Pool WETH balance");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function test_revert_deploy_invalid_discovery_supply_share() public {
|
|
252
|
+
bytes memory poolConfig = _generatePoolConfig(
|
|
253
|
+
CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
|
|
254
|
+
address(weth),
|
|
255
|
+
DEFAULT_DISCOVERY_TICK_LOWER,
|
|
256
|
+
DEFAULT_DISCOVERY_TICK_UPPER,
|
|
257
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
258
|
+
1.1e18
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
vm.expectRevert(abi.encodeWithSelector(IDopplerErrors.MaxShareToBeSoldExceeded.selector, 1.1e18, 1e18));
|
|
262
|
+
factory.deploy(users.creator, _getDefaultOwners(), "https://test.com", "Testcoin", "TEST", poolConfig, users.platformReferrer, 0);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function test_alignTick_isToken0_positive() public pure {
|
|
266
|
+
int24 tick = 12345;
|
|
267
|
+
int24 TICK_SPACING = 60;
|
|
268
|
+
int24 expected = 12300;
|
|
269
|
+
|
|
270
|
+
assertEq(CoinDopplerUniV3.alignTickToTickSpacing(true, tick, TICK_SPACING), expected, "Align positive tick (token0)");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function test_alignTick_isToken0_negative() public pure {
|
|
274
|
+
int24 tick = -12345;
|
|
275
|
+
int24 TICK_SPACING = 60;
|
|
276
|
+
int24 expected = -12360;
|
|
277
|
+
assertEq(CoinDopplerUniV3.alignTickToTickSpacing(true, tick, TICK_SPACING), expected, "Align negative tick (token0)");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function test_alignTick_isToken1_negative() public pure {
|
|
281
|
+
int24 tick = -12345;
|
|
282
|
+
int24 TICK_SPACING = 60;
|
|
283
|
+
int24 expected = -12300;
|
|
284
|
+
assertEq(CoinDopplerUniV3.alignTickToTickSpacing(false, tick, TICK_SPACING), expected, "Align negative tick (token1)");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function test_alignTick_isToken1_zero() public pure {
|
|
288
|
+
int24 tick = 0;
|
|
289
|
+
int24 expected = 0;
|
|
290
|
+
assertEq(CoinDopplerUniV3.alignTickToTickSpacing(false, tick, MarketConstants.TICK_SPACING), expected, "Align zero tick (token1)");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Additional tick alignment test for full branch coverage
|
|
294
|
+
function test_alignTick_isToken0_zero() public pure {
|
|
295
|
+
int24 tick = 0;
|
|
296
|
+
int24 expected = 0;
|
|
297
|
+
assertEq(CoinDopplerUniV3.alignTickToTickSpacing(true, tick, MarketConstants.TICK_SPACING), expected, "Align zero tick (token0)");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function test_alignTick_isToken1_positive() public pure {
|
|
301
|
+
int24 tick = 12345;
|
|
302
|
+
int24 expected = 12400; // Round up for token1
|
|
303
|
+
assertEq(CoinDopplerUniV3.alignTickToTickSpacing(false, tick, MarketConstants.TICK_SPACING), expected, "Align positive tick (token1)");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function test_calculateLpTail_isToken0() public pure {
|
|
307
|
+
int24 tickLower = DEFAULT_DISCOVERY_TICK_LOWER;
|
|
308
|
+
int24 tickUpper = DEFAULT_DISCOVERY_TICK_UPPER;
|
|
309
|
+
uint256 tailSupply = 1e18;
|
|
310
|
+
bool isToken0 = true;
|
|
311
|
+
|
|
312
|
+
LpPosition memory tail = CoinDopplerUniV3.calculateLpTail(tickLower, tickUpper, isToken0, tailSupply, MarketConstants.TICK_SPACING);
|
|
313
|
+
|
|
314
|
+
int24 expectedPosTickLower = tickUpper;
|
|
315
|
+
int24 expectedPosTickUpper = CoinDopplerUniV3.alignTickToTickSpacing(true, TickMath.MAX_TICK, MarketConstants.TICK_SPACING);
|
|
316
|
+
|
|
317
|
+
assertEq(tail.tickLower, expectedPosTickLower, "Tail tickLower (token0)");
|
|
318
|
+
assertEq(tail.tickUpper, expectedPosTickUpper, "Tail tickUpper (token0)");
|
|
319
|
+
assertGt(tail.liquidity, 0, "Tail liquidity should be > 0 (token0)");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function test_calculateLpTail_isToken1() public pure {
|
|
323
|
+
int24 tickLower = DEFAULT_DISCOVERY_TICK_LOWER;
|
|
324
|
+
int24 tickUpper = DEFAULT_DISCOVERY_TICK_UPPER;
|
|
325
|
+
uint256 tailSupply = 1e18;
|
|
326
|
+
bool isToken0 = false;
|
|
327
|
+
|
|
328
|
+
LpPosition memory tail = CoinDopplerUniV3.calculateLpTail(tickLower, tickUpper, isToken0, tailSupply, MarketConstants.TICK_SPACING);
|
|
329
|
+
|
|
330
|
+
int24 expectedPosTickLower = CoinDopplerUniV3.alignTickToTickSpacing(false, TickMath.MIN_TICK, MarketConstants.TICK_SPACING);
|
|
331
|
+
int24 expectedPosTickUpper = tickLower;
|
|
332
|
+
|
|
333
|
+
assertEq(tail.tickLower, expectedPosTickLower, "Tail tickLower (token1)");
|
|
334
|
+
assertEq(tail.tickUpper, expectedPosTickUpper, "Tail tickUpper (token1)");
|
|
335
|
+
assertGt(tail.liquidity, 0, "Tail liquidity should be > 0 (token1)");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function test_calculateLogNormalDistribution_isToken0() public pure {
|
|
339
|
+
int24 tickLower = -60000;
|
|
340
|
+
int24 tickUpper = 0;
|
|
341
|
+
bool isToken0 = true;
|
|
342
|
+
uint256 discoverySupply = 100e18;
|
|
343
|
+
LpPosition[] memory newPositions = new LpPosition[](DEFAULT_NUM_DISCOVERY_POSITIONS);
|
|
344
|
+
|
|
345
|
+
(LpPosition[] memory positions, uint256 totalAssetsSold) = CoinDopplerUniV3.calculateLogNormalDistribution(
|
|
346
|
+
tickLower,
|
|
347
|
+
tickUpper,
|
|
348
|
+
MarketConstants.TICK_SPACING,
|
|
349
|
+
isToken0,
|
|
350
|
+
discoverySupply,
|
|
351
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
352
|
+
newPositions
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
assertEq(positions.length, DEFAULT_NUM_DISCOVERY_POSITIONS, "Correct number of positions (token0)");
|
|
356
|
+
assertTrue(totalAssetsSold <= discoverySupply, "Total assets sold <= discovery supply (token0)");
|
|
357
|
+
assertTrue(totalAssetsSold > 0, "Total assets sold > 0 (token0)");
|
|
358
|
+
|
|
359
|
+
int24 expectedFarTick = tickUpper; // 0
|
|
360
|
+
for (uint i = 0; i < DEFAULT_NUM_DISCOVERY_POSITIONS; i++) {
|
|
361
|
+
assertTrue(positions[i].liquidity > 0, "Position liquidity > 0 (token0)");
|
|
362
|
+
assertTrue(positions[i].tickLower <= positions[i].tickUpper, "Tick order check (token0)");
|
|
363
|
+
assertEq(positions[i].tickLower % MarketConstants.TICK_SPACING, 0, "Lower tick alignment (token0)");
|
|
364
|
+
assertEq(positions[i].tickUpper % MarketConstants.TICK_SPACING, 0, "Upper tick alignment (token0)");
|
|
365
|
+
assertEq(positions[i].tickUpper, expectedFarTick, "Far tick check (token0)");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function test_calculateLogNormalDistribution_isToken1() public pure {
|
|
370
|
+
int24 tickLower = -60000;
|
|
371
|
+
int24 tickUpper = 0;
|
|
372
|
+
bool isToken0 = false;
|
|
373
|
+
uint256 discoverySupply = 100e18;
|
|
374
|
+
LpPosition[] memory newPositions = new LpPosition[](DEFAULT_NUM_DISCOVERY_POSITIONS);
|
|
375
|
+
|
|
376
|
+
(LpPosition[] memory positions, uint256 totalAssetsSold) = CoinDopplerUniV3.calculateLogNormalDistribution(
|
|
377
|
+
tickLower,
|
|
378
|
+
tickUpper,
|
|
379
|
+
MarketConstants.TICK_SPACING,
|
|
380
|
+
isToken0,
|
|
381
|
+
discoverySupply,
|
|
382
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
383
|
+
newPositions
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
assertEq(positions.length, DEFAULT_NUM_DISCOVERY_POSITIONS, "Correct number of positions (token1)");
|
|
387
|
+
assertTrue(totalAssetsSold <= discoverySupply, "Total assets sold <= discovery supply (token1)");
|
|
388
|
+
assertTrue(totalAssetsSold > 0, "Total assets sold > 0 (token1)");
|
|
389
|
+
|
|
390
|
+
int24 expectedFarTick = tickLower; // -60000
|
|
391
|
+
for (uint i = 0; i < DEFAULT_NUM_DISCOVERY_POSITIONS; i++) {
|
|
392
|
+
assertTrue(positions[i].liquidity > 0, "Position liquidity > 0 (token1)");
|
|
393
|
+
assertTrue(positions[i].tickLower <= positions[i].tickUpper, "Tick order check (token1)");
|
|
394
|
+
assertEq(positions[i].tickLower % MarketConstants.TICK_SPACING, 0, "Lower tick alignment (token1)");
|
|
395
|
+
assertEq(positions[i].tickUpper % MarketConstants.TICK_SPACING, 0, "Upper tick alignment (token1)");
|
|
396
|
+
assertEq(positions[i].tickLower, expectedFarTick, "Far tick check (token1)");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function test_calculateLogNormalDistribution_zeroDiscoverySupply() public pure {
|
|
401
|
+
int24 tickLower = -60000;
|
|
402
|
+
int24 tickUpper = 0;
|
|
403
|
+
bool isToken0 = true;
|
|
404
|
+
uint256 discoverySupply = 0;
|
|
405
|
+
LpPosition[] memory newPositions = new LpPosition[](DEFAULT_NUM_DISCOVERY_POSITIONS);
|
|
406
|
+
|
|
407
|
+
(LpPosition[] memory positions, uint256 totalAssetsSold) = CoinDopplerUniV3.calculateLogNormalDistribution(
|
|
408
|
+
tickLower,
|
|
409
|
+
tickUpper,
|
|
410
|
+
MarketConstants.TICK_SPACING,
|
|
411
|
+
isToken0,
|
|
412
|
+
discoverySupply,
|
|
413
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
414
|
+
newPositions
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
assertEq(positions.length, DEFAULT_NUM_DISCOVERY_POSITIONS, "Correct number of positions (zero supply)");
|
|
418
|
+
assertEq(totalAssetsSold, 0, "Total assets sold is 0 (zero supply)");
|
|
419
|
+
|
|
420
|
+
for (uint i = 0; i < DEFAULT_NUM_DISCOVERY_POSITIONS; i++) {
|
|
421
|
+
assertEq(positions[i].liquidity, 0, "Position liquidity is 0 (zero supply)");
|
|
422
|
+
assertTrue(positions[i].tickLower <= positions[i].tickUpper, "Tick order check (zero supply)");
|
|
423
|
+
assertEq(positions[i].tickLower % MarketConstants.TICK_SPACING, 0, "Lower tick alignment (zero supply)");
|
|
424
|
+
assertEq(positions[i].tickUpper % MarketConstants.TICK_SPACING, 0, "Upper tick alignment (zero supply)");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function test_calculateLogNormalDistribution_startingTickEqualsFarTick() public pure {
|
|
429
|
+
// Will force startingTick == farTick
|
|
430
|
+
int24 tickLower = 0;
|
|
431
|
+
int24 tickUpper = 0;
|
|
432
|
+
|
|
433
|
+
bool isToken0 = true;
|
|
434
|
+
uint256 discoverySupply = 100e18;
|
|
435
|
+
uint16 totalPositions = 1;
|
|
436
|
+
LpPosition[] memory newPositions = new LpPosition[](totalPositions);
|
|
437
|
+
|
|
438
|
+
(LpPosition[] memory positions, uint256 totalAssetsSold) = CoinDopplerUniV3.calculateLogNormalDistribution(
|
|
439
|
+
tickLower,
|
|
440
|
+
tickUpper,
|
|
441
|
+
MarketConstants.TICK_SPACING,
|
|
442
|
+
isToken0,
|
|
443
|
+
discoverySupply,
|
|
444
|
+
totalPositions,
|
|
445
|
+
newPositions
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
assertEq(positions.length, totalPositions, "Correct number of positions");
|
|
449
|
+
assertEq(totalAssetsSold, 0, "No assets sold when startingTick equals farTick");
|
|
450
|
+
assertEq(positions[0].liquidity, 0, "Position should have zero liquidity");
|
|
451
|
+
}
|
|
452
|
+
}
|
package/test/Factory.t.sol
CHANGED
|
@@ -25,7 +25,7 @@ contract FactoryTest is BaseTest {
|
|
|
25
25
|
"TEST2",
|
|
26
26
|
users.platformReferrer,
|
|
27
27
|
address(weth),
|
|
28
|
-
LP_TICK_LOWER_WETH,
|
|
28
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
29
29
|
0
|
|
30
30
|
);
|
|
31
31
|
coin = Coin(payable(coinAddress));
|
|
@@ -74,7 +74,7 @@ contract FactoryTest is BaseTest {
|
|
|
74
74
|
"TEST2",
|
|
75
75
|
users.platformReferrer,
|
|
76
76
|
address(weth),
|
|
77
|
-
LP_TICK_LOWER_WETH,
|
|
77
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
78
78
|
initialOrderSize
|
|
79
79
|
);
|
|
80
80
|
coin = Coin(payable(coinAddress));
|
|
@@ -120,7 +120,7 @@ contract FactoryTest is BaseTest {
|
|
|
120
120
|
"TEST2",
|
|
121
121
|
users.platformReferrer,
|
|
122
122
|
address(weth),
|
|
123
|
-
LP_TICK_LOWER_WETH,
|
|
123
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
124
124
|
initialOrderSize
|
|
125
125
|
);
|
|
126
126
|
}
|
|
@@ -140,7 +140,7 @@ contract FactoryTest is BaseTest {
|
|
|
140
140
|
"TEST2",
|
|
141
141
|
users.platformReferrer,
|
|
142
142
|
address(weth),
|
|
143
|
-
LP_TICK_LOWER_WETH,
|
|
143
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
144
144
|
orderSize
|
|
145
145
|
);
|
|
146
146
|
coin = Coin(payable(coinAddress));
|
|
@@ -173,6 +173,39 @@ contract FactoryTest is BaseTest {
|
|
|
173
173
|
assertEq(coin.payoutRecipient(), users.creator, "payoutRecipient");
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
function test_deploy_with_usdc_order() public {
|
|
177
|
+
address[] memory owners = new address[](1);
|
|
178
|
+
owners[0] = users.creator;
|
|
179
|
+
|
|
180
|
+
uint256 orderSize = dealUSDC(users.creator, 100);
|
|
181
|
+
|
|
182
|
+
vm.prank(users.creator);
|
|
183
|
+
usdc.approve(address(factory), orderSize);
|
|
184
|
+
|
|
185
|
+
assertEq(usdc.balanceOf(users.creator), orderSize);
|
|
186
|
+
assertEq(usdc.allowance(users.creator, address(factory)), orderSize);
|
|
187
|
+
|
|
188
|
+
vm.prank(users.creator);
|
|
189
|
+
(address coinAddress, uint256 coinsPurchased) = factory.deploy(
|
|
190
|
+
users.creator,
|
|
191
|
+
owners,
|
|
192
|
+
"https://testcoinusdcpair.com",
|
|
193
|
+
"Testcoinusdcpair",
|
|
194
|
+
"TESTCOINUSDCPAIR",
|
|
195
|
+
users.platformReferrer,
|
|
196
|
+
USDC_ADDRESS,
|
|
197
|
+
USDC_TICK_LOWER,
|
|
198
|
+
orderSize
|
|
199
|
+
);
|
|
200
|
+
coin = Coin(payable(coinAddress));
|
|
201
|
+
pool = IUniswapV3Pool(coin.poolAddress());
|
|
202
|
+
vm.label(address(coin), "COIN");
|
|
203
|
+
vm.label(address(pool), "POOL");
|
|
204
|
+
|
|
205
|
+
assertEq(coin.currency(), USDC_ADDRESS, "currency");
|
|
206
|
+
assertEq(coin.balanceOf(users.creator), CREATOR_LAUNCH_REWARD + coinsPurchased);
|
|
207
|
+
}
|
|
208
|
+
|
|
176
209
|
function test_deploy_with_usdc_revert_payout_recipient_zero() public {
|
|
177
210
|
address[] memory owners = new address[](1);
|
|
178
211
|
owners[0] = users.creator;
|
|
@@ -234,8 +267,17 @@ contract FactoryTest is BaseTest {
|
|
|
234
267
|
owners[0] = users.creator;
|
|
235
268
|
|
|
236
269
|
vm.expectRevert(abi.encodeWithSelector(ICoin.InvalidWethLowerTick.selector));
|
|
237
|
-
|
|
238
|
-
|
|
270
|
+
factory.deploy(
|
|
271
|
+
users.creator,
|
|
272
|
+
owners,
|
|
273
|
+
"https://testcoin.com",
|
|
274
|
+
"Testcoin",
|
|
275
|
+
"TESTCOIN",
|
|
276
|
+
users.platformReferrer,
|
|
277
|
+
address(0),
|
|
278
|
+
MarketConstants.LP_TICK_LOWER_WETH + 1,
|
|
279
|
+
0
|
|
280
|
+
);
|
|
239
281
|
}
|
|
240
282
|
|
|
241
283
|
function test_deploy_with_usdc_revert_invalid_eth_transfer() public {
|
|
@@ -274,7 +316,7 @@ contract FactoryTest is BaseTest {
|
|
|
274
316
|
"TEST",
|
|
275
317
|
users.platformReferrer,
|
|
276
318
|
address(weth),
|
|
277
|
-
LP_TICK_LOWER_WETH,
|
|
319
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
278
320
|
0
|
|
279
321
|
);
|
|
280
322
|
coin = Coin(payable(coinAddress));
|
package/test/utils/BaseTest.sol
CHANGED
|
@@ -17,19 +17,23 @@ import {MultiOwnable} from "../../src/utils/MultiOwnable.sol";
|
|
|
17
17
|
import {ICoin} from "../../src/interfaces/ICoin.sol";
|
|
18
18
|
import {IERC7572} from "../../src/interfaces/IERC7572.sol";
|
|
19
19
|
import {IWETH} from "../../src/interfaces/IWETH.sol";
|
|
20
|
+
import {IAirlock} from "../../src/interfaces/IAirlock.sol";
|
|
20
21
|
import {INonfungiblePositionManager} from "../../src/interfaces/INonfungiblePositionManager.sol";
|
|
21
22
|
import {ISwapRouter} from "../../src/interfaces/ISwapRouter.sol";
|
|
23
|
+
import {IUniswapV3Factory} from "../../src/interfaces/IUniswapV3Factory.sol";
|
|
22
24
|
import {IUniswapV3Pool} from "../../src/interfaces/IUniswapV3Pool.sol";
|
|
23
25
|
import {IProtocolRewards} from "../../src/interfaces/IProtocolRewards.sol";
|
|
24
26
|
import {ProtocolRewards} from "../utils/ProtocolRewards.sol";
|
|
25
|
-
|
|
27
|
+
import {MarketConstants} from "../../src/libs/MarketConstants.sol";
|
|
26
28
|
contract BaseTest is Test, CoinConstants {
|
|
27
29
|
using stdStorage for StdStorage;
|
|
28
30
|
|
|
29
31
|
address internal constant PROTOCOL_REWARDS = 0x7777777F279eba3d3Ad8F4E708545291A6fDBA8B;
|
|
30
32
|
address internal constant WETH_ADDRESS = 0x4200000000000000000000000000000000000006;
|
|
33
|
+
address internal constant V3_FACTORY = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD;
|
|
31
34
|
address internal constant NONFUNGIBLE_POSITION_MANAGER = 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1;
|
|
32
35
|
address internal constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481;
|
|
36
|
+
address internal constant DOPPLER_AIRLOCK = 0x660eAaEdEBc968f8f3694354FA8EC0b4c5Ba8D12;
|
|
33
37
|
address internal constant USDC_ADDRESS = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
|
|
34
38
|
int24 internal constant USDC_TICK_LOWER = 57200;
|
|
35
39
|
|
|
@@ -48,8 +52,10 @@ contract BaseTest is Test, CoinConstants {
|
|
|
48
52
|
IERC20Metadata internal usdc;
|
|
49
53
|
IWETH internal weth;
|
|
50
54
|
ProtocolRewards internal protocolRewards;
|
|
55
|
+
IUniswapV3Factory internal v3Factory;
|
|
51
56
|
INonfungiblePositionManager internal nonfungiblePositionManager;
|
|
52
57
|
ISwapRouter internal swapRouter;
|
|
58
|
+
IAirlock internal airlock;
|
|
53
59
|
Users internal users;
|
|
54
60
|
|
|
55
61
|
Coin internal coinImpl;
|
|
@@ -59,12 +65,14 @@ contract BaseTest is Test, CoinConstants {
|
|
|
59
65
|
IUniswapV3Pool internal pool;
|
|
60
66
|
|
|
61
67
|
function setUp() public virtual {
|
|
62
|
-
forkId = vm.createSelectFork("base",
|
|
68
|
+
forkId = vm.createSelectFork("base", 28415528);
|
|
63
69
|
|
|
64
70
|
weth = IWETH(WETH_ADDRESS);
|
|
65
71
|
usdc = IERC20Metadata(USDC_ADDRESS);
|
|
72
|
+
v3Factory = IUniswapV3Factory(V3_FACTORY);
|
|
66
73
|
nonfungiblePositionManager = INonfungiblePositionManager(NONFUNGIBLE_POSITION_MANAGER);
|
|
67
74
|
swapRouter = ISwapRouter(SWAP_ROUTER);
|
|
75
|
+
airlock = IAirlock(DOPPLER_AIRLOCK);
|
|
68
76
|
protocolRewards = new ProtocolRewards();
|
|
69
77
|
|
|
70
78
|
users = Users({
|
|
@@ -78,7 +86,7 @@ contract BaseTest is Test, CoinConstants {
|
|
|
78
86
|
tradeReferrer: makeAddr("tradeReferrer")
|
|
79
87
|
});
|
|
80
88
|
|
|
81
|
-
coinImpl = new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS,
|
|
89
|
+
coinImpl = new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, V3_FACTORY, SWAP_ROUTER, DOPPLER_AIRLOCK);
|
|
82
90
|
factoryImpl = new ZoraFactoryImpl(address(coinImpl));
|
|
83
91
|
factory = ZoraFactoryImpl(address(new ZoraFactory(address(factoryImpl))));
|
|
84
92
|
|
|
@@ -87,9 +95,11 @@ contract BaseTest is Test, CoinConstants {
|
|
|
87
95
|
vm.label(address(factory), "ZORA_FACTORY");
|
|
88
96
|
vm.label(address(protocolRewards), "PROTOCOL_REWARDS");
|
|
89
97
|
vm.label(address(nonfungiblePositionManager), "NONFUNGIBLE_POSITION_MANAGER");
|
|
98
|
+
vm.label(address(v3Factory), "V3_FACTORY");
|
|
90
99
|
vm.label(address(swapRouter), "SWAP_ROUTER");
|
|
91
100
|
vm.label(address(weth), "WETH");
|
|
92
101
|
vm.label(address(usdc), "USDC");
|
|
102
|
+
vm.label(address(airlock), "AIRLOCK");
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
struct TradeRewards {
|
|
@@ -102,6 +112,7 @@ contract BaseTest is Test, CoinConstants {
|
|
|
102
112
|
struct MarketRewards {
|
|
103
113
|
uint256 creator;
|
|
104
114
|
uint256 platformReferrer;
|
|
115
|
+
uint256 doppler;
|
|
105
116
|
uint256 protocol;
|
|
106
117
|
}
|
|
107
118
|
|
|
@@ -118,7 +129,7 @@ contract BaseTest is Test, CoinConstants {
|
|
|
118
129
|
"TEST",
|
|
119
130
|
users.platformReferrer,
|
|
120
131
|
address(weth),
|
|
121
|
-
LP_TICK_LOWER_WETH,
|
|
132
|
+
MarketConstants.LP_TICK_LOWER_WETH,
|
|
122
133
|
0
|
|
123
134
|
);
|
|
124
135
|
|
|
@@ -171,9 +182,10 @@ contract BaseTest is Test, CoinConstants {
|
|
|
171
182
|
function _calculateMarketRewards(uint256 ethAmount) internal pure returns (MarketRewards memory) {
|
|
172
183
|
uint256 creator = (ethAmount * 5000) / 10_000;
|
|
173
184
|
uint256 platformReferrer = (ethAmount * 2500) / 10_000;
|
|
174
|
-
uint256
|
|
185
|
+
uint256 doppler = (ethAmount * 500) / 10_000;
|
|
186
|
+
uint256 protocol = ethAmount - creator - platformReferrer - doppler;
|
|
175
187
|
|
|
176
|
-
return MarketRewards({creator: creator, platformReferrer: platformReferrer, protocol: protocol});
|
|
188
|
+
return MarketRewards({creator: creator, platformReferrer: platformReferrer, doppler: doppler, protocol: protocol});
|
|
177
189
|
}
|
|
178
190
|
|
|
179
191
|
function dealUSDC(address to, uint256 numUSDC) internal returns (uint256) {
|
|
@@ -182,5 +194,12 @@ contract BaseTest is Test, CoinConstants {
|
|
|
182
194
|
return amount;
|
|
183
195
|
}
|
|
184
196
|
|
|
185
|
-
function
|
|
197
|
+
function _getDefaultOwners() internal view returns (address[] memory owners) {
|
|
198
|
+
owners = new address[](1);
|
|
199
|
+
owners[0] = users.creator;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function dopplerFeeRecipient() internal view returns (address) {
|
|
203
|
+
return airlock.owner();
|
|
204
|
+
}
|
|
186
205
|
}
|
package/wagmi.config.ts
CHANGED