@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
package/src/Coin.sol
CHANGED
|
@@ -3,12 +3,13 @@ pragma solidity ^0.8.23;
|
|
|
3
3
|
|
|
4
4
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
5
5
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
6
|
-
import {ICoin} from "./interfaces/ICoin.sol";
|
|
6
|
+
import {ICoin, PoolConfiguration} from "./interfaces/ICoin.sol";
|
|
7
7
|
import {ICoinComments} from "./interfaces/ICoinComments.sol";
|
|
8
8
|
import {IERC7572} from "./interfaces/IERC7572.sol";
|
|
9
|
-
import {
|
|
9
|
+
import {IUniswapV3Factory} from "./interfaces/IUniswapV3Factory.sol";
|
|
10
10
|
import {IUniswapV3Pool} from "./interfaces/IUniswapV3Pool.sol";
|
|
11
11
|
import {ISwapRouter} from "./interfaces/ISwapRouter.sol";
|
|
12
|
+
import {IAirlock} from "./interfaces/IAirlock.sol";
|
|
12
13
|
import {IProtocolRewards} from "./interfaces/IProtocolRewards.sol";
|
|
13
14
|
import {IWETH} from "./interfaces/IWETH.sol";
|
|
14
15
|
|
|
@@ -19,7 +20,13 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
|
|
|
19
20
|
import {ContractVersionBase} from "./version/ContractVersionBase.sol";
|
|
20
21
|
import {CoinConstants} from "./utils/CoinConstants.sol";
|
|
21
22
|
import {MultiOwnable} from "./utils/MultiOwnable.sol";
|
|
22
|
-
import {
|
|
23
|
+
import {FullMath} from "./utils/uniswap/FullMath.sol";
|
|
24
|
+
import {TickMath} from "./utils/uniswap/TickMath.sol";
|
|
25
|
+
import {LiquidityAmounts} from "./utils/uniswap/LiquidityAmounts.sol";
|
|
26
|
+
import {CoinSetup} from "./libs/CoinSetup.sol";
|
|
27
|
+
import {MarketConstants} from "./libs/MarketConstants.sol";
|
|
28
|
+
import {LpPosition} from "./types/LpPosition.sol";
|
|
29
|
+
import {PoolState} from "./types/PoolState.sol";
|
|
23
30
|
|
|
24
31
|
/*
|
|
25
32
|
$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\
|
|
@@ -34,25 +41,85 @@ import {TickMath} from "./utils/TickMath.sol";
|
|
|
34
41
|
contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeable, MultiOwnable, ReentrancyGuardUpgradeable {
|
|
35
42
|
using SafeERC20 for IERC20;
|
|
36
43
|
|
|
44
|
+
/// @notice The address of the WETH contract
|
|
37
45
|
address public immutable WETH;
|
|
38
|
-
address
|
|
46
|
+
/// @notice The address of the Uniswap V3 factory
|
|
47
|
+
address public immutable v3Factory;
|
|
48
|
+
/// @notice The address of the Uniswap V3 swap router
|
|
39
49
|
address public immutable swapRouter;
|
|
50
|
+
/// @notice The address of the Airlock contract, ownership is used for a protocol fee split.
|
|
51
|
+
address public immutable airlock;
|
|
52
|
+
/// @notice The address of the protocol rewards contract
|
|
40
53
|
address public immutable protocolRewards;
|
|
54
|
+
/// @notice The address of the protocol reward recipient
|
|
41
55
|
address public immutable protocolRewardRecipient;
|
|
42
56
|
|
|
57
|
+
/// @notice The metadata URI
|
|
58
|
+
string public tokenURI;
|
|
59
|
+
/// @notice The address of the coin creator
|
|
43
60
|
address public payoutRecipient;
|
|
61
|
+
/// @notice The address of the platform referrer
|
|
44
62
|
address public platformReferrer;
|
|
63
|
+
/// @notice The address of the Uniswap V3 pool
|
|
45
64
|
address public poolAddress;
|
|
65
|
+
/// @notice The address of the currency
|
|
46
66
|
address public currency;
|
|
47
|
-
uint256 public lpTokenId;
|
|
48
|
-
string public tokenURI;
|
|
49
67
|
|
|
68
|
+
PoolConfiguration public poolConfiguration;
|
|
69
|
+
|
|
70
|
+
/// @notice Returns the state of the pool
|
|
71
|
+
/// @dev This is a legacy function for compatibility with doppler default state
|
|
72
|
+
/// @return asset The address of the asset
|
|
73
|
+
/// @return numeraire The address of the numeraire
|
|
74
|
+
/// @return tickLower The lower tick
|
|
75
|
+
/// @return tickUpper The upper tick
|
|
76
|
+
/// @return numPositions The number of discovery positions
|
|
77
|
+
/// @return isInitialized Whether the pool is initialized
|
|
78
|
+
/// @return isExited Whether the pool is exited
|
|
79
|
+
/// @return maxShareToBeSold The maximum share to be sold
|
|
80
|
+
/// @return totalTokensOnBondingCurve The total tokens on the bonding curve
|
|
81
|
+
function poolState()
|
|
82
|
+
external
|
|
83
|
+
view
|
|
84
|
+
returns (
|
|
85
|
+
address asset,
|
|
86
|
+
address numeraire,
|
|
87
|
+
int24 tickLower,
|
|
88
|
+
int24 tickUpper,
|
|
89
|
+
uint16 numPositions,
|
|
90
|
+
bool isInitialized,
|
|
91
|
+
bool isExited,
|
|
92
|
+
uint256 maxShareToBeSold,
|
|
93
|
+
uint256 totalTokensOnBondingCurve
|
|
94
|
+
)
|
|
95
|
+
{
|
|
96
|
+
asset = address(this);
|
|
97
|
+
numeraire = currency;
|
|
98
|
+
tickLower = poolConfiguration.tickLower;
|
|
99
|
+
tickUpper = poolConfiguration.tickUpper;
|
|
100
|
+
numPositions = poolConfiguration.numPositions;
|
|
101
|
+
isInitialized = true;
|
|
102
|
+
isExited = false;
|
|
103
|
+
maxShareToBeSold = poolConfiguration.maxDiscoverySupplyShare;
|
|
104
|
+
totalTokensOnBondingCurve = POOL_LAUNCH_SUPPLY;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @notice The constructor for the static Coin contract deployment shared across all Coins.
|
|
109
|
+
* @param _protocolRewardRecipient The address of the protocol reward recipient
|
|
110
|
+
* @param _protocolRewards The address of the protocol rewards contract
|
|
111
|
+
* @param _weth The address of the WETH contract
|
|
112
|
+
* @param _v3Factory The address of the Uniswap V3 factory
|
|
113
|
+
* @param _swapRouter The address of the Uniswap V3 swap router
|
|
114
|
+
* @param _airlock The address of the Airlock contract, ownership is used for a protocol fee split.
|
|
115
|
+
*/
|
|
50
116
|
constructor(
|
|
51
117
|
address _protocolRewardRecipient,
|
|
52
118
|
address _protocolRewards,
|
|
53
119
|
address _weth,
|
|
54
|
-
address
|
|
55
|
-
address _swapRouter
|
|
120
|
+
address _v3Factory,
|
|
121
|
+
address _swapRouter,
|
|
122
|
+
address _airlock
|
|
56
123
|
) initializer {
|
|
57
124
|
if (_protocolRewardRecipient == address(0)) {
|
|
58
125
|
revert AddressZero();
|
|
@@ -63,18 +130,22 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
63
130
|
if (_weth == address(0)) {
|
|
64
131
|
revert AddressZero();
|
|
65
132
|
}
|
|
66
|
-
if (
|
|
133
|
+
if (_v3Factory == address(0)) {
|
|
67
134
|
revert AddressZero();
|
|
68
135
|
}
|
|
69
136
|
if (_swapRouter == address(0)) {
|
|
70
137
|
revert AddressZero();
|
|
71
138
|
}
|
|
139
|
+
if (_airlock == address(0)) {
|
|
140
|
+
revert AddressZero();
|
|
141
|
+
}
|
|
72
142
|
|
|
73
143
|
protocolRewardRecipient = _protocolRewardRecipient;
|
|
74
144
|
protocolRewards = _protocolRewards;
|
|
75
145
|
WETH = _weth;
|
|
76
|
-
nonfungiblePositionManager = _nonfungiblePositionManager;
|
|
77
146
|
swapRouter = _swapRouter;
|
|
147
|
+
v3Factory = _v3Factory;
|
|
148
|
+
airlock = _airlock;
|
|
78
149
|
}
|
|
79
150
|
|
|
80
151
|
/// @notice Initializes a new coin
|
|
@@ -82,18 +153,16 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
82
153
|
/// @param tokenURI_ The metadata URI
|
|
83
154
|
/// @param name_ The coin name
|
|
84
155
|
/// @param symbol_ The coin symbol
|
|
156
|
+
/// @param poolConfig_ The parameters for the v3 pool and liquidity
|
|
85
157
|
/// @param platformReferrer_ The address of the platform referrer
|
|
86
|
-
/// @param currency_ The address of the currency
|
|
87
|
-
/// @param tickLower_ The tick lower for the Uniswap V3 pool; ignored for ETH/WETH
|
|
88
158
|
function initialize(
|
|
89
159
|
address payoutRecipient_,
|
|
90
160
|
address[] memory owners_,
|
|
91
161
|
string memory tokenURI_,
|
|
92
162
|
string memory name_,
|
|
93
163
|
string memory symbol_,
|
|
94
|
-
|
|
95
|
-
address
|
|
96
|
-
int24 tickLower_
|
|
164
|
+
bytes memory poolConfig_,
|
|
165
|
+
address platformReferrer_
|
|
97
166
|
) public initializer {
|
|
98
167
|
// Validate the creation parameters
|
|
99
168
|
if (payoutRecipient_ == address(0)) {
|
|
@@ -110,9 +179,8 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
110
179
|
_setPayoutRecipient(payoutRecipient_);
|
|
111
180
|
_setContractURI(tokenURI_);
|
|
112
181
|
|
|
113
|
-
//
|
|
182
|
+
// Store the referrer if set
|
|
114
183
|
platformReferrer = platformReferrer_ == address(0) ? protocolRewardRecipient : platformReferrer_;
|
|
115
|
-
currency = currency_ == address(0) ? WETH : currency_;
|
|
116
184
|
|
|
117
185
|
// Mint the total supply
|
|
118
186
|
_mint(address(this), MAX_TOTAL_SUPPLY);
|
|
@@ -120,11 +188,8 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
120
188
|
// Distribute the creator launch reward
|
|
121
189
|
_transfer(address(this), payoutRecipient, CREATOR_LAUNCH_REWARD);
|
|
122
190
|
|
|
123
|
-
// Approve the transfer of the remaining supply to the pool
|
|
124
|
-
IERC20(address(this)).safeIncreaseAllowance(address(nonfungiblePositionManager), POOL_LAUNCH_SUPPLY);
|
|
125
|
-
|
|
126
191
|
// Deploy the pool
|
|
127
|
-
|
|
192
|
+
_deployLiquidity(poolConfig_);
|
|
128
193
|
}
|
|
129
194
|
|
|
130
195
|
/// @notice Executes a buy order
|
|
@@ -157,7 +222,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
157
222
|
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
158
223
|
tokenIn: currency,
|
|
159
224
|
tokenOut: address(this),
|
|
160
|
-
fee: LP_FEE,
|
|
225
|
+
fee: MarketConstants.LP_FEE,
|
|
161
226
|
recipient: recipient,
|
|
162
227
|
amountIn: trueOrderSize,
|
|
163
228
|
amountOutMinimum: minAmountOut,
|
|
@@ -207,7 +272,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
207
272
|
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
208
273
|
tokenIn: address(this),
|
|
209
274
|
tokenOut: currency,
|
|
210
|
-
fee: LP_FEE,
|
|
275
|
+
fee: MarketConstants.LP_FEE,
|
|
211
276
|
recipient: address(this),
|
|
212
277
|
amountIn: orderSize,
|
|
213
278
|
amountOutMinimum: minAmountOut,
|
|
@@ -257,6 +322,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
257
322
|
/// @notice Enables a user to burn their tokens
|
|
258
323
|
/// @param amount The amount of tokens to burn
|
|
259
324
|
function burn(uint256 amount) external {
|
|
325
|
+
// This burn function sets the from as msg.sender, so having an unauthed call is safe.
|
|
260
326
|
_burn(msg.sender, amount);
|
|
261
327
|
}
|
|
262
328
|
|
|
@@ -302,24 +368,17 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
302
368
|
|
|
303
369
|
/// @notice Receives ETH converted from WETH
|
|
304
370
|
receive() external payable {
|
|
305
|
-
|
|
306
|
-
revert OnlyWeth();
|
|
307
|
-
}
|
|
371
|
+
require(msg.sender == WETH, OnlyWeth());
|
|
308
372
|
}
|
|
309
373
|
|
|
310
|
-
/// @dev
|
|
311
|
-
function
|
|
374
|
+
/// @dev Called by the pool after minting liquidity to transfer the associated coins
|
|
375
|
+
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
|
|
312
376
|
if (msg.sender != poolAddress) revert OnlyPool();
|
|
313
377
|
|
|
314
|
-
|
|
378
|
+
IERC20(address(this)).safeTransfer(poolAddress, amount0Owed == 0 ? amount1Owed : amount0Owed);
|
|
315
379
|
}
|
|
316
380
|
|
|
317
|
-
/// @dev
|
|
318
|
-
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {}
|
|
319
|
-
|
|
320
|
-
/// @dev Overrides ERC20's _update function to
|
|
321
|
-
/// - Prevent transfers to the pool if the market has not graduated.
|
|
322
|
-
/// - Emit the superset `WowTokenTransfer` event with each ERC20 transfer.
|
|
381
|
+
/// @dev Overrides ERC20's _update function to emit a superset `CoinTransfer` event
|
|
323
382
|
function _update(address from, address to, uint256 value) internal virtual override {
|
|
324
383
|
super._update(from, to, value);
|
|
325
384
|
|
|
@@ -347,57 +406,43 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
347
406
|
tokenURI = newURI;
|
|
348
407
|
}
|
|
349
408
|
|
|
350
|
-
/// @dev
|
|
351
|
-
function
|
|
352
|
-
|
|
353
|
-
if (currency == WETH && tickLower_ < LP_TICK_LOWER_WETH) {
|
|
354
|
-
revert InvalidWethLowerTick();
|
|
355
|
-
}
|
|
409
|
+
/// @dev Deploys the Uniswap V3 pool and mints initial liquidity based on the pool configuration
|
|
410
|
+
function _deployLiquidity(bytes memory poolConfig_) internal {
|
|
411
|
+
(uint8 version, address currency_) = abi.decode(poolConfig_, (uint8, address));
|
|
356
412
|
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
if (currency != WETH && (tickLower_ >= LP_TICK_UPPER || tickLower_ % 200 != 0)) {
|
|
360
|
-
revert InvalidCurrencyLowerTick();
|
|
361
|
-
}
|
|
413
|
+
// Store the currency, defaulting to WETH if address(0)
|
|
414
|
+
currency = currency_ == address(0) ? WETH : currency_;
|
|
362
415
|
|
|
363
416
|
// Sort the token addresses
|
|
364
417
|
address token0 = address(this) < currency ? address(this) : currency;
|
|
365
418
|
address token1 = address(this) < currency ? currency : address(this);
|
|
366
|
-
|
|
367
|
-
// If the coin is token0
|
|
368
419
|
bool isCoinToken0 = token0 == address(this);
|
|
369
420
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// Construct the LP data
|
|
385
|
-
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
|
|
386
|
-
token0: token0,
|
|
387
|
-
token1: token1,
|
|
388
|
-
fee: LP_FEE,
|
|
389
|
-
tickLower: tickLower,
|
|
390
|
-
tickUpper: tickUpper,
|
|
391
|
-
amount0Desired: amount0,
|
|
392
|
-
amount1Desired: amount1,
|
|
393
|
-
amount0Min: 0,
|
|
394
|
-
amount1Min: 0,
|
|
395
|
-
recipient: address(this),
|
|
396
|
-
deadline: block.timestamp
|
|
397
|
-
});
|
|
421
|
+
(uint160 sqrtPriceX96, PoolConfiguration memory _poolConfig) = CoinSetup.setupPoolWithVersion(version, poolConfig_, isCoinToken0, WETH);
|
|
422
|
+
|
|
423
|
+
poolConfiguration = _poolConfig;
|
|
424
|
+
|
|
425
|
+
poolAddress = _createPool(token0, token1, sqrtPriceX96);
|
|
426
|
+
|
|
427
|
+
LpPosition[] memory positions = CoinSetup.calculatePositions(isCoinToken0, poolConfiguration);
|
|
428
|
+
|
|
429
|
+
_mintPositions(positions);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/// @dev Creates the Uniswap V3 pool for the coin/currency pair
|
|
433
|
+
function _createPool(address token0, address token1, uint160 sqrtPriceX96) internal returns (address pool) {
|
|
434
|
+
pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
|
|
398
435
|
|
|
399
|
-
//
|
|
400
|
-
|
|
436
|
+
// This pool should be new, if it has already been initialized
|
|
437
|
+
// then we will fail the creation step prompting the user to try again.
|
|
438
|
+
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
|
|
442
|
+
function _mintPositions(LpPosition[] memory lbpPositions) internal {
|
|
443
|
+
for (uint256 i; i < lbpPositions.length; i++) {
|
|
444
|
+
IUniswapV3Pool(poolAddress).mint(address(this), lbpPositions[i].tickLower, lbpPositions[i].tickUpper, lbpPositions[i].liquidity, "");
|
|
445
|
+
}
|
|
401
446
|
}
|
|
402
447
|
|
|
403
448
|
/// @dev Handles incoming currency transfers for buy orders; if WETH is the currency the caller has the option to send native-ETH
|
|
@@ -499,15 +544,31 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
499
544
|
);
|
|
500
545
|
}
|
|
501
546
|
|
|
547
|
+
/// @dev Collects and distributes accrued fees from all LP positions
|
|
502
548
|
function _handleMarketRewards() internal returns (MarketRewards memory) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
549
|
+
uint256 totalAmountToken0;
|
|
550
|
+
uint256 totalAmountToken1;
|
|
551
|
+
uint256 amount0;
|
|
552
|
+
uint256 amount1;
|
|
553
|
+
|
|
554
|
+
bool isCoinToken0 = address(this) < currency;
|
|
555
|
+
LpPosition[] memory positions = CoinSetup.calculatePositions(isCoinToken0, poolConfiguration);
|
|
556
|
+
|
|
557
|
+
for (uint256 i; i < positions.length; i++) {
|
|
558
|
+
// Must burn to update the collect mapping on the pool
|
|
559
|
+
IUniswapV3Pool(poolAddress).burn(positions[i].tickLower, positions[i].tickUpper, 0);
|
|
560
|
+
|
|
561
|
+
(amount0, amount1) = IUniswapV3Pool(poolAddress).collect(
|
|
562
|
+
address(this),
|
|
563
|
+
positions[i].tickLower,
|
|
564
|
+
positions[i].tickUpper,
|
|
565
|
+
type(uint128).max,
|
|
566
|
+
type(uint128).max
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
totalAmountToken0 += amount0;
|
|
570
|
+
totalAmountToken1 += amount1;
|
|
571
|
+
}
|
|
511
572
|
|
|
512
573
|
address token0 = currency < address(this) ? currency : address(this);
|
|
513
574
|
address token1 = currency < address(this) ? address(this) : currency;
|
|
@@ -524,9 +585,11 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
524
585
|
|
|
525
586
|
function _transferMarketRewards(address token, uint256 totalAmount, MarketRewards memory rewards) internal returns (MarketRewards memory) {
|
|
526
587
|
if (totalAmount > 0) {
|
|
588
|
+
address dopplerRecipient = IAirlock(airlock).owner();
|
|
589
|
+
uint256 dopplerPayout = _calculateReward(totalAmount, DOPPLER_MARKET_REWARD_BPS);
|
|
527
590
|
uint256 creatorPayout = _calculateReward(totalAmount, CREATOR_MARKET_REWARD_BPS);
|
|
528
591
|
uint256 platformReferrerPayout = _calculateReward(totalAmount, PLATFORM_REFERRER_MARKET_REWARD_BPS);
|
|
529
|
-
uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout;
|
|
592
|
+
uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout - dopplerPayout;
|
|
530
593
|
|
|
531
594
|
if (token == WETH) {
|
|
532
595
|
IWETH(WETH).withdraw(totalAmount);
|
|
@@ -536,22 +599,26 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
536
599
|
rewards.platformReferrerAmountCurrency = platformReferrerPayout;
|
|
537
600
|
rewards.protocolAmountCurrency = protocolPayout;
|
|
538
601
|
|
|
539
|
-
address[] memory recipients = new address[](
|
|
602
|
+
address[] memory recipients = new address[](4);
|
|
540
603
|
recipients[0] = payoutRecipient;
|
|
541
604
|
recipients[1] = platformReferrer;
|
|
542
605
|
recipients[2] = protocolRewardRecipient;
|
|
606
|
+
recipients[3] = dopplerRecipient;
|
|
543
607
|
|
|
544
|
-
uint256[] memory amounts = new uint256[](
|
|
608
|
+
uint256[] memory amounts = new uint256[](4);
|
|
545
609
|
amounts[0] = rewards.creatorPayoutAmountCurrency;
|
|
546
610
|
amounts[1] = rewards.platformReferrerAmountCurrency;
|
|
547
611
|
amounts[2] = rewards.protocolAmountCurrency;
|
|
612
|
+
amounts[3] = dopplerPayout;
|
|
548
613
|
|
|
549
|
-
bytes4[] memory reasons = new bytes4[](
|
|
614
|
+
bytes4[] memory reasons = new bytes4[](4);
|
|
550
615
|
reasons[0] = bytes4(keccak256("COIN_CREATOR_MARKET_REWARD"));
|
|
551
616
|
reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_MARKET_REWARD"));
|
|
552
617
|
reasons[2] = bytes4(keccak256("COIN_PROTOCOL_MARKET_REWARD"));
|
|
618
|
+
reasons[3] = bytes4(keccak256("COIN_DOPPLER_MARKET_REWARD"));
|
|
553
619
|
|
|
554
620
|
IProtocolRewards(protocolRewards).depositBatch{value: totalAmount}(recipients, amounts, reasons, "");
|
|
621
|
+
IProtocolRewards(protocolRewards).withdrawFor(dopplerRecipient, dopplerPayout);
|
|
555
622
|
} else if (token == address(this)) {
|
|
556
623
|
rewards.totalAmountCoin = totalAmount;
|
|
557
624
|
rewards.creatorPayoutAmountCoin = creatorPayout;
|
|
@@ -561,6 +628,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
561
628
|
_transfer(address(this), payoutRecipient, rewards.creatorPayoutAmountCoin);
|
|
562
629
|
_transfer(address(this), platformReferrer, rewards.platformReferrerAmountCoin);
|
|
563
630
|
_transfer(address(this), protocolRewardRecipient, rewards.protocolAmountCoin);
|
|
631
|
+
_transfer(address(this), dopplerRecipient, dopplerPayout);
|
|
564
632
|
} else {
|
|
565
633
|
rewards.totalAmountCurrency = totalAmount;
|
|
566
634
|
rewards.creatorPayoutAmountCurrency = creatorPayout;
|
|
@@ -570,6 +638,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
570
638
|
IERC20(currency).safeTransfer(payoutRecipient, creatorPayout);
|
|
571
639
|
IERC20(currency).safeTransfer(platformReferrer, platformReferrerPayout);
|
|
572
640
|
IERC20(currency).safeTransfer(protocolRewardRecipient, protocolPayout);
|
|
641
|
+
IERC20(currency).safeTransfer(dopplerRecipient, dopplerPayout);
|
|
573
642
|
}
|
|
574
643
|
}
|
|
575
644
|
|
package/src/ZoraFactoryImpl.sol
CHANGED
|
@@ -8,6 +8,7 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
|
|
|
8
8
|
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
|
|
9
9
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
10
10
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
11
|
+
import {CoinConfigurationVersions} from "./libs/CoinConfigurationVersions.sol";
|
|
11
12
|
|
|
12
13
|
import {IZoraFactory} from "./interfaces/IZoraFactory.sol";
|
|
13
14
|
import {Coin} from "./Coin.sol";
|
|
@@ -22,6 +23,49 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
|
|
|
22
23
|
coinImpl = _coinImpl;
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/// @notice Creates a new coin contract
|
|
27
|
+
/// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
|
|
28
|
+
/// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
|
|
29
|
+
/// @param uri The coin metadata uri
|
|
30
|
+
/// @param name The name of the coin
|
|
31
|
+
/// @param symbol The symbol of the coin
|
|
32
|
+
/// @param poolConfig The config parameters for the Uniswap v3 pool; `abi.encode(address currency, int24 tickLower, int24 tickUpper, uint16 numDiscoveryPositions, uint256 maxDiscoverySupplyShare)`
|
|
33
|
+
/// @param platformReferrer The address of the platform referrer
|
|
34
|
+
/// @param orderSize The order size for the first buy; must match msg.value for ETH/WETH pairs
|
|
35
|
+
function deploy(
|
|
36
|
+
address payoutRecipient,
|
|
37
|
+
address[] memory owners,
|
|
38
|
+
string memory uri,
|
|
39
|
+
string memory name,
|
|
40
|
+
string memory symbol,
|
|
41
|
+
bytes memory poolConfig,
|
|
42
|
+
address platformReferrer,
|
|
43
|
+
uint256 orderSize
|
|
44
|
+
) public payable nonReentrant returns (address, uint256) {
|
|
45
|
+
bytes32 salt = _generateSalt(payoutRecipient, uri);
|
|
46
|
+
|
|
47
|
+
Coin coin = Coin(payable(Clones.cloneDeterministic(coinImpl, salt)));
|
|
48
|
+
|
|
49
|
+
coin.initialize(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
|
|
50
|
+
|
|
51
|
+
uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
|
|
52
|
+
|
|
53
|
+
emit CoinCreated(
|
|
54
|
+
msg.sender,
|
|
55
|
+
payoutRecipient,
|
|
56
|
+
coin.platformReferrer(),
|
|
57
|
+
coin.currency(),
|
|
58
|
+
uri,
|
|
59
|
+
name,
|
|
60
|
+
symbol,
|
|
61
|
+
address(coin),
|
|
62
|
+
coin.poolAddress(),
|
|
63
|
+
coin.contractVersion()
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return (address(coin), coinsPurchased);
|
|
67
|
+
}
|
|
68
|
+
|
|
25
69
|
/// @notice Creates a new coin contract
|
|
26
70
|
/// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
|
|
27
71
|
/// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
|
|
@@ -47,7 +91,9 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
|
|
|
47
91
|
|
|
48
92
|
Coin coin = Coin(payable(Clones.cloneDeterministic(coinImpl, salt)));
|
|
49
93
|
|
|
50
|
-
|
|
94
|
+
bytes memory poolConfig = abi.encode(CoinConfigurationVersions.LEGACY_POOL_VERSION, currency, tickLower);
|
|
95
|
+
|
|
96
|
+
coin.initialize(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
|
|
51
97
|
|
|
52
98
|
uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
|
|
53
99
|
|
package/src/interfaces/ICoin.sol
CHANGED
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
5
|
-
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
6
5
|
import {IERC7572} from "./IERC7572.sol";
|
|
6
|
+
import {IDopplerErrors} from "./IDopplerErrors.sol";
|
|
7
|
+
|
|
8
|
+
/// @notice The configuration of the pool
|
|
9
|
+
/// @dev This is used to configure the pool's liquidity positions
|
|
10
|
+
struct PoolConfiguration {
|
|
11
|
+
uint8 version;
|
|
12
|
+
int24 tickLower;
|
|
13
|
+
int24 tickUpper;
|
|
14
|
+
uint16 numPositions;
|
|
15
|
+
uint256 maxDiscoverySupplyShare;
|
|
16
|
+
}
|
|
7
17
|
|
|
8
|
-
interface ICoin is IERC165,
|
|
18
|
+
interface ICoin is IERC165, IERC7572, IDopplerErrors {
|
|
9
19
|
/// @notice Thrown when an operation is attempted with a zero address
|
|
10
20
|
error AddressZero();
|
|
11
21
|
|
|
@@ -57,6 +67,12 @@ interface ICoin is IERC165, IERC721Receiver, IERC7572 {
|
|
|
57
67
|
/// @notice Thrown when the lower tick is not set to the default value
|
|
58
68
|
error InvalidWethLowerTick();
|
|
59
69
|
|
|
70
|
+
/// @notice Thrown when a legacy pool does not have one discovery position
|
|
71
|
+
error LegacyPoolMustHaveOneDiscoveryPosition();
|
|
72
|
+
|
|
73
|
+
/// @notice Thrown when a Doppler pool does not have more than 2 discovery positions
|
|
74
|
+
error DopplerPoolMustHaveMoreThan2DiscoveryPositions();
|
|
75
|
+
|
|
60
76
|
/// @notice The rewards accrued from the market's liquidity position
|
|
61
77
|
struct MarketRewards {
|
|
62
78
|
uint256 totalAmountCurrency;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
interface IDopplerErrors {
|
|
5
|
+
error NumDiscoveryPositionsOutOfRange();
|
|
6
|
+
|
|
7
|
+
error CannotMintZeroLiquidity();
|
|
8
|
+
|
|
9
|
+
/// @notice Thrown when the tick range is misordered
|
|
10
|
+
error InvalidTickRangeMisordered(int24 tickLower, int24 tickUpper);
|
|
11
|
+
|
|
12
|
+
/// @notice Thrown when the max share to be sold exceeds the maximum unit
|
|
13
|
+
error MaxShareToBeSoldExceeded(uint256 value, uint256 limit);
|
|
14
|
+
}
|
|
@@ -94,6 +94,8 @@ interface INonfungiblePositionManager {
|
|
|
94
94
|
|
|
95
95
|
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
|
|
96
96
|
|
|
97
|
+
function factory() external view returns (address);
|
|
98
|
+
|
|
97
99
|
/// @notice Emitted when tokens are collected for a position NFT
|
|
98
100
|
/// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior
|
|
99
101
|
/// @param tokenId The ID of the token for which underlying tokens were collected
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
|
+
pragma solidity >=0.5.0;
|
|
3
|
+
|
|
4
|
+
/// @title The interface for the Uniswap V3 Factory
|
|
5
|
+
/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees
|
|
6
|
+
interface IUniswapV3Factory {
|
|
7
|
+
/// @notice Emitted when the owner of the factory is changed
|
|
8
|
+
/// @param oldOwner The owner before the owner was changed
|
|
9
|
+
/// @param newOwner The owner after the owner was changed
|
|
10
|
+
event OwnerChanged(address indexed oldOwner, address indexed newOwner);
|
|
11
|
+
|
|
12
|
+
/// @notice Emitted when a pool is created
|
|
13
|
+
/// @param token0 The first token of the pool by address sort order
|
|
14
|
+
/// @param token1 The second token of the pool by address sort order
|
|
15
|
+
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
|
|
16
|
+
/// @param tickSpacing The minimum number of ticks between initialized ticks
|
|
17
|
+
/// @param pool The address of the created pool
|
|
18
|
+
event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool);
|
|
19
|
+
|
|
20
|
+
/// @notice Emitted when a new fee amount is enabled for pool creation via the factory
|
|
21
|
+
/// @param fee The enabled fee, denominated in hundredths of a bip
|
|
22
|
+
/// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee
|
|
23
|
+
event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing);
|
|
24
|
+
|
|
25
|
+
/// @notice Returns the current owner of the factory
|
|
26
|
+
/// @dev Can be changed by the current owner via setOwner
|
|
27
|
+
/// @return The address of the factory owner
|
|
28
|
+
function owner() external view returns (address);
|
|
29
|
+
|
|
30
|
+
/// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled
|
|
31
|
+
/// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context
|
|
32
|
+
/// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee
|
|
33
|
+
/// @return The tick spacing
|
|
34
|
+
function feeAmountTickSpacing(uint24 fee) external view returns (int24);
|
|
35
|
+
|
|
36
|
+
/// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
|
|
37
|
+
/// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
|
|
38
|
+
/// @param tokenA The contract address of either token0 or token1
|
|
39
|
+
/// @param tokenB The contract address of the other token
|
|
40
|
+
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
|
|
41
|
+
/// @return pool The pool address
|
|
42
|
+
function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);
|
|
43
|
+
|
|
44
|
+
/// @notice Creates a pool for the given two tokens and fee
|
|
45
|
+
/// @param tokenA One of the two tokens in the desired pool
|
|
46
|
+
/// @param tokenB The other of the two tokens in the desired pool
|
|
47
|
+
/// @param fee The desired fee for the pool
|
|
48
|
+
/// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved
|
|
49
|
+
/// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments
|
|
50
|
+
/// are invalid.
|
|
51
|
+
/// @return pool The address of the newly created pool
|
|
52
|
+
function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool);
|
|
53
|
+
|
|
54
|
+
/// @notice Updates the owner of the factory
|
|
55
|
+
/// @dev Must be called by the current owner
|
|
56
|
+
/// @param _owner The new owner of the factory
|
|
57
|
+
function setOwner(address _owner) external;
|
|
58
|
+
|
|
59
|
+
/// @notice Enables a fee amount with the given tickSpacing
|
|
60
|
+
/// @dev Fee amounts may never be removed once enabled
|
|
61
|
+
/// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6)
|
|
62
|
+
/// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount
|
|
63
|
+
function enableFeeAmount(uint24 fee, int24 tickSpacing) external;
|
|
64
|
+
}
|