@zoralabs/coins 0.7.0 → 0.9.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/.env +1 -0
- package/.turbo/turbo-build.log +114 -109
- package/.turbo/turbo-update-contract-version.log +22 -0
- package/CHANGELOG.md +34 -0
- package/abis/BadImpl.json +15 -0
- package/abis/BalanceDeltaLibrary.json +15 -0
- package/abis/BaseCoin.json +1442 -0
- package/abis/BaseCoinDeployHook.json +78 -0
- package/abis/BaseHook.json +897 -0
- package/abis/BaseTest.json +13 -91
- package/abis/BeforeSwapDeltaLibrary.json +15 -0
- package/abis/BuySupplyWithSwapRouterHook.json +126 -0
- package/abis/Coin.json +48 -92
- package/abis/CoinConstants.json +65 -0
- package/abis/CoinTest.json +27 -91
- package/abis/CoinV4.json +1664 -0
- package/abis/CurrencyLibrary.json +25 -0
- package/abis/DeployHooks.json +9 -0
- package/abis/DopplerUniswapV3Test.json +20 -91
- package/abis/FactoryTest.json +13 -91
- package/abis/FakeHookNoInterface.json +21 -0
- package/abis/HookDeployer.json +68 -0
- package/abis/Hooks.json +28 -0
- package/abis/HooksTest.json +651 -0
- package/abis/IAllowanceTransfer.json +486 -0
- package/abis/ICoin.json +25 -1
- package/abis/ICoinDeployHook.json +31 -0
- package/abis/IContractMetadata.json +28 -0
- package/abis/IEIP712.json +15 -0
- package/abis/IEIP712_v4.json +15 -0
- package/abis/IERC20Minimal.json +172 -0
- package/abis/IERC6909Claims.json +288 -0
- package/abis/IERC721Permit_v4.json +88 -0
- package/abis/IExtsload.json +64 -0
- package/abis/IExttload.json +40 -0
- package/abis/IHasAfterCoinDeploy.json +31 -0
- package/abis/IHasContractName.json +15 -0
- package/abis/IHookDeployer.json +42 -0
- package/abis/IHooks.json +789 -0
- package/abis/IImmutableState.json +15 -0
- package/abis/IMulticall_v4.json +21 -0
- package/abis/INotifier.json +187 -0
- package/abis/IPermit2.json +865 -0
- package/abis/IPermit2Forwarder.json +138 -0
- package/abis/IPoolInitializer_v4.json +53 -0
- package/abis/IPoolManager.json +1286 -0
- package/abis/IPositionManager.json +712 -0
- package/abis/IProtocolFees.json +174 -0
- package/abis/ISignatureTransfer.json +394 -0
- package/abis/ISubscriber.json +89 -0
- package/abis/ISwapRouter.json +82 -0
- package/abis/IUnorderedNonce.json +44 -0
- package/abis/IV4Router.json +47 -0
- package/abis/IZoraFactory.json +144 -0
- package/abis/IZoraV4CoinHook.json +83 -0
- package/abis/ImmutableState.json +36 -0
- package/abis/LPFeeLibrary.json +65 -0
- package/abis/MultiOwnableTest.json +13 -91
- package/abis/Simulate.json +0 -91
- package/abis/UniV3BuySell.json +12 -0
- package/abis/UniV3Errors.json +32 -0
- package/abis/UpgradeFactoryImpl.json +9 -0
- package/abis/UpgradesTest.json +604 -0
- package/abis/ZoraFactoryImpl.json +111 -0
- package/abis/ZoraV4CoinHook.json +989 -0
- package/addresses/8453.json +2 -1
- package/addresses/84532.json +4 -3
- package/dist/index.cjs +125 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +120 -56
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +212 -464
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +122 -66
- package/package.json +4 -4
- package/script/CoinsDeployerBase.sol +32 -0
- package/script/DeployHooks.s.sol +22 -0
- package/script/Simulate.s.sol +3 -2
- package/script/UpgradeCoinImpl.sol +2 -2
- package/script/UpgradeFactoryImpl.s.sol +23 -0
- package/src/Coin.sol +35 -342
- package/src/ZoraFactoryImpl.sol +73 -45
- package/src/hooks/BaseCoinDeployHook.sol +62 -0
- package/src/hooks/BuySupplyWithSwapRouterHook.sol +78 -0
- package/src/interfaces/ICoin.sol +5 -1
- package/src/interfaces/ICoinDeployHook.sol +8 -0
- package/src/interfaces/ISwapRouter.sol +1 -35
- package/src/interfaces/IZoraFactory.sol +52 -0
- package/src/{utils → libs}/CoinConstants.sol +6 -6
- package/src/libs/CoinLegacy.sol +4 -4
- package/src/libs/CoinLegacyMarket.sol +182 -0
- package/src/libs/CoinSetupV3.sol +111 -0
- package/src/libs/UniV3BuySell.sol +449 -0
- package/src/libs/UniV3Errors.sol +11 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Coin.t.sol +35 -17
- package/test/CoinDopplerUniV3.t.sol +14 -17
- package/test/Factory.t.sol +4 -3
- package/test/Hooks.t.sol +274 -0
- package/test/Upgrades.t.sol +67 -0
- package/test/utils/BaseTest.sol +18 -3
- package/wagmi.config.ts +6 -9
- package/src/libs/CoinSetup.sol +0 -37
- /package/abis/{CoinSetup.json → CoinSetupV3.json} +0 -0
package/src/Coin.sol
CHANGED
|
@@ -18,15 +18,16 @@ import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/
|
|
|
18
18
|
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
|
|
19
19
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
20
20
|
import {ContractVersionBase} from "./version/ContractVersionBase.sol";
|
|
21
|
-
import {CoinConstants} from "./utils/CoinConstants.sol";
|
|
22
21
|
import {MultiOwnable} from "./utils/MultiOwnable.sol";
|
|
23
22
|
import {FullMath} from "./utils/uniswap/FullMath.sol";
|
|
24
23
|
import {TickMath} from "./utils/uniswap/TickMath.sol";
|
|
25
24
|
import {LiquidityAmounts} from "./utils/uniswap/LiquidityAmounts.sol";
|
|
26
|
-
import {
|
|
25
|
+
import {CoinConstants} from "./libs/CoinConstants.sol";
|
|
27
26
|
import {MarketConstants} from "./libs/MarketConstants.sol";
|
|
28
27
|
import {LpPosition} from "./types/LpPosition.sol";
|
|
29
28
|
import {PoolState} from "./types/PoolState.sol";
|
|
29
|
+
import {CoinSetupV3, UniV3Config, CoinV3Config} from "./libs/CoinSetupV3.sol";
|
|
30
|
+
import {UniV3BuySell, CoinConfig} from "./libs/UniV3BuySell.sol";
|
|
30
31
|
|
|
31
32
|
/*
|
|
32
33
|
$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\
|
|
@@ -38,7 +39,7 @@ import {PoolState} from "./types/PoolState.sol";
|
|
|
38
39
|
\$$$$$$ | $$$$$$ |$$$$$$\ $$ | \$$ |
|
|
39
40
|
\______/ \______/ \______|\__| \__|
|
|
40
41
|
*/
|
|
41
|
-
contract Coin is ICoin,
|
|
42
|
+
contract Coin is ICoin, ContractVersionBase, ERC20PermitUpgradeable, MultiOwnable, ReentrancyGuardUpgradeable {
|
|
42
43
|
using SafeERC20 for IERC20;
|
|
43
44
|
|
|
44
45
|
/// @notice The address of the WETH contract
|
|
@@ -65,6 +66,11 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
65
66
|
/// @notice The address of the currency
|
|
66
67
|
address public currency;
|
|
67
68
|
|
|
69
|
+
/// @notice The state of the market
|
|
70
|
+
bytes public market;
|
|
71
|
+
uint8 public marketVersion;
|
|
72
|
+
|
|
73
|
+
/// @notice deprecated
|
|
68
74
|
PoolConfiguration public poolConfiguration;
|
|
69
75
|
|
|
70
76
|
/// @notice Returns the state of the pool
|
|
@@ -101,7 +107,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
101
107
|
isInitialized = true;
|
|
102
108
|
isExited = false;
|
|
103
109
|
maxShareToBeSold = poolConfiguration.maxDiscoverySupplyShare;
|
|
104
|
-
totalTokensOnBondingCurve = POOL_LAUNCH_SUPPLY;
|
|
110
|
+
totalTokensOnBondingCurve = CoinConstants.POOL_LAUNCH_SUPPLY;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/**
|
|
@@ -183,13 +189,31 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
183
189
|
platformReferrer = platformReferrer_ == address(0) ? protocolRewardRecipient : platformReferrer_;
|
|
184
190
|
|
|
185
191
|
// Mint the total supply
|
|
186
|
-
_mint(address(this), MAX_TOTAL_SUPPLY);
|
|
192
|
+
_mint(address(this), CoinConstants.MAX_TOTAL_SUPPLY);
|
|
187
193
|
|
|
188
194
|
// Distribute the creator launch reward
|
|
189
|
-
_transfer(address(this), payoutRecipient, CREATOR_LAUNCH_REWARD);
|
|
195
|
+
_transfer(address(this), payoutRecipient, CoinConstants.CREATOR_LAUNCH_REWARD);
|
|
196
|
+
|
|
197
|
+
UniV3Config memory uniswapV3Config = UniV3Config({weth: WETH, v3Factory: v3Factory, swapRouter: swapRouter, airlock: airlock});
|
|
190
198
|
|
|
191
199
|
// Deploy the pool
|
|
192
|
-
|
|
200
|
+
(currency, poolAddress, poolConfiguration) = CoinSetupV3.setupPool(poolConfig_, uniswapV3Config, address(this));
|
|
201
|
+
|
|
202
|
+
// Split out the deployment of liquidity to avoid stack too deep
|
|
203
|
+
CoinSetupV3.deployLiquidity(address(this), currency, poolConfiguration, poolAddress);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildCoinConfig() internal view returns (CoinConfig memory coinConfig) {
|
|
207
|
+
coinConfig = CoinConfig({
|
|
208
|
+
protocolRewardRecipient: protocolRewardRecipient,
|
|
209
|
+
platformReferrer: platformReferrer,
|
|
210
|
+
currency: currency,
|
|
211
|
+
payoutRecipient: payoutRecipient,
|
|
212
|
+
protocolRewards: protocolRewards,
|
|
213
|
+
poolConfiguration: poolConfiguration,
|
|
214
|
+
poolAddress: poolAddress,
|
|
215
|
+
uniswapV3Config: UniV3Config({weth: WETH, v3Factory: v3Factory, swapRouter: swapRouter, airlock: airlock})
|
|
216
|
+
});
|
|
193
217
|
}
|
|
194
218
|
|
|
195
219
|
/// @notice Executes a buy order
|
|
@@ -204,41 +228,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
204
228
|
uint160 sqrtPriceLimitX96,
|
|
205
229
|
address tradeReferrer
|
|
206
230
|
) public payable nonReentrant returns (uint256, uint256) {
|
|
207
|
-
|
|
208
|
-
if (recipient == address(0)) {
|
|
209
|
-
revert AddressZero();
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Calculate the trade reward
|
|
213
|
-
uint256 tradeReward = _calculateReward(orderSize, TOTAL_FEE_BPS);
|
|
214
|
-
|
|
215
|
-
// Calculate the remaining size
|
|
216
|
-
uint256 trueOrderSize = orderSize - tradeReward;
|
|
217
|
-
|
|
218
|
-
// Handle incoming currency
|
|
219
|
-
_handleIncomingCurrency(orderSize, trueOrderSize);
|
|
220
|
-
|
|
221
|
-
// Set up the swap parameters
|
|
222
|
-
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
223
|
-
tokenIn: currency,
|
|
224
|
-
tokenOut: address(this),
|
|
225
|
-
fee: MarketConstants.LP_FEE,
|
|
226
|
-
recipient: recipient,
|
|
227
|
-
amountIn: trueOrderSize,
|
|
228
|
-
amountOutMinimum: minAmountOut,
|
|
229
|
-
sqrtPriceLimitX96: sqrtPriceLimitX96
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Execute the swap
|
|
233
|
-
uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
|
|
234
|
-
|
|
235
|
-
_handleTradeRewards(tradeReward, tradeReferrer);
|
|
236
|
-
|
|
237
|
-
_handleMarketRewards();
|
|
238
|
-
|
|
239
|
-
emit CoinBuy(msg.sender, recipient, tradeReferrer, amountOut, currency, tradeReward, trueOrderSize);
|
|
240
|
-
|
|
241
|
-
return (orderSize, amountOut);
|
|
231
|
+
return UniV3BuySell.buy(recipient, orderSize, minAmountOut, sqrtPriceLimitX96, tradeReferrer, address(this), buildCoinConfig());
|
|
242
232
|
}
|
|
243
233
|
|
|
244
234
|
/// @notice Executes a sell order
|
|
@@ -254,11 +244,6 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
254
244
|
uint160 sqrtPriceLimitX96,
|
|
255
245
|
address tradeReferrer
|
|
256
246
|
) public nonReentrant returns (uint256, uint256) {
|
|
257
|
-
// Ensure the recipient is not the zero address
|
|
258
|
-
if (recipient == address(0)) {
|
|
259
|
-
revert AddressZero();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
247
|
// Record the coin balance of this contract before the swap
|
|
263
248
|
uint256 beforeCoinBalance = balanceOf(address(this));
|
|
264
249
|
|
|
@@ -268,55 +253,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
268
253
|
// Approve the Uniswap V3 swap router
|
|
269
254
|
this.approve(swapRouter, orderSize);
|
|
270
255
|
|
|
271
|
-
|
|
272
|
-
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
273
|
-
tokenIn: address(this),
|
|
274
|
-
tokenOut: currency,
|
|
275
|
-
fee: MarketConstants.LP_FEE,
|
|
276
|
-
recipient: address(this),
|
|
277
|
-
amountIn: orderSize,
|
|
278
|
-
amountOutMinimum: minAmountOut,
|
|
279
|
-
sqrtPriceLimitX96: sqrtPriceLimitX96
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Execute the swap
|
|
283
|
-
uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
|
|
284
|
-
|
|
285
|
-
// Record the coin balance of this contract after the swap
|
|
286
|
-
uint256 afterCoinBalance = balanceOf(address(this));
|
|
287
|
-
|
|
288
|
-
// If the swap was partially executed:
|
|
289
|
-
if (afterCoinBalance > beforeCoinBalance) {
|
|
290
|
-
// Calculate the refund
|
|
291
|
-
uint256 coinRefund = afterCoinBalance - beforeCoinBalance;
|
|
292
|
-
|
|
293
|
-
// Update the order size
|
|
294
|
-
orderSize -= coinRefund;
|
|
295
|
-
|
|
296
|
-
// Transfer the refund back to the seller
|
|
297
|
-
_transfer(address(this), recipient, coinRefund);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// If currency is WETH, convert to ETH
|
|
301
|
-
if (currency == WETH) {
|
|
302
|
-
IWETH(WETH).withdraw(amountOut);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Calculate the trade reward
|
|
306
|
-
uint256 tradeReward = _calculateReward(amountOut, TOTAL_FEE_BPS);
|
|
307
|
-
|
|
308
|
-
// Calculate the payout after the fee
|
|
309
|
-
uint256 payoutSize = amountOut - tradeReward;
|
|
310
|
-
|
|
311
|
-
_handlePayout(payoutSize, recipient);
|
|
312
|
-
|
|
313
|
-
_handleTradeRewards(tradeReward, tradeReferrer);
|
|
314
|
-
|
|
315
|
-
_handleMarketRewards();
|
|
316
|
-
|
|
317
|
-
emit CoinSell(msg.sender, recipient, tradeReferrer, orderSize, currency, tradeReward, payoutSize);
|
|
318
|
-
|
|
319
|
-
return (orderSize, payoutSize);
|
|
256
|
+
return UniV3BuySell.sell(recipient, beforeCoinBalance, orderSize, minAmountOut, sqrtPriceLimitX96, tradeReferrer, buildCoinConfig());
|
|
320
257
|
}
|
|
321
258
|
|
|
322
259
|
/// @notice Enables a user to burn their tokens
|
|
@@ -330,7 +267,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
330
267
|
/// @dev This function is a fallback, secondary rewards will be claimed automatically on each buy and sell.
|
|
331
268
|
/// @param pushEthRewards Whether to push the ETH directly to the recipients.
|
|
332
269
|
function claimSecondaryRewards(bool pushEthRewards) external nonReentrant {
|
|
333
|
-
MarketRewards memory rewards =
|
|
270
|
+
MarketRewards memory rewards = UniV3BuySell.handleMarketRewards(address(this), buildCoinConfig());
|
|
334
271
|
|
|
335
272
|
if (pushEthRewards && rewards.totalAmountCurrency > 0 && currency == WETH) {
|
|
336
273
|
IProtocolRewards(protocolRewards).withdrawFor(payoutRecipient, rewards.creatorPayoutAmountCurrency);
|
|
@@ -373,7 +310,7 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
373
310
|
|
|
374
311
|
/// @dev Called by the pool after minting liquidity to transfer the associated coins
|
|
375
312
|
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
|
|
376
|
-
if (msg.sender != poolAddress) revert OnlyPool();
|
|
313
|
+
if (msg.sender != poolAddress) revert OnlyPool(msg.sender, poolAddress);
|
|
377
314
|
|
|
378
315
|
IERC20(address(this)).safeTransfer(poolAddress, amount0Owed == 0 ? amount1Owed : amount0Owed);
|
|
379
316
|
}
|
|
@@ -405,248 +342,4 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
|
|
|
405
342
|
|
|
406
343
|
tokenURI = newURI;
|
|
407
344
|
}
|
|
408
|
-
|
|
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));
|
|
412
|
-
|
|
413
|
-
// Store the currency, defaulting to WETH if address(0)
|
|
414
|
-
currency = currency_ == address(0) ? WETH : currency_;
|
|
415
|
-
|
|
416
|
-
// Sort the token addresses
|
|
417
|
-
address token0 = address(this) < currency ? address(this) : currency;
|
|
418
|
-
address token1 = address(this) < currency ? currency : address(this);
|
|
419
|
-
bool isCoinToken0 = token0 == address(this);
|
|
420
|
-
|
|
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);
|
|
435
|
-
|
|
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
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/// @dev Handles incoming currency transfers for buy orders; if WETH is the currency the caller has the option to send native-ETH
|
|
449
|
-
/// @param orderSize The total size of the order in the currency
|
|
450
|
-
/// @param trueOrderSize The actual amount being used for the swap after fees
|
|
451
|
-
function _handleIncomingCurrency(uint256 orderSize, uint256 trueOrderSize) internal {
|
|
452
|
-
if (currency == WETH && msg.value > 0) {
|
|
453
|
-
if (msg.value != orderSize) {
|
|
454
|
-
revert EthAmountMismatch();
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (msg.value < MIN_ORDER_SIZE) {
|
|
458
|
-
revert EthAmountTooSmall();
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
IWETH(WETH).deposit{value: trueOrderSize}();
|
|
462
|
-
IWETH(WETH).approve(swapRouter, trueOrderSize);
|
|
463
|
-
} else {
|
|
464
|
-
// Ensure ETH is not sent with a non-ETH pair
|
|
465
|
-
if (msg.value != 0) {
|
|
466
|
-
revert EthTransferInvalid();
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
uint256 beforeBalance = IERC20(currency).balanceOf(address(this));
|
|
470
|
-
IERC20(currency).safeTransferFrom(msg.sender, address(this), orderSize);
|
|
471
|
-
uint256 afterBalance = IERC20(currency).balanceOf(address(this));
|
|
472
|
-
|
|
473
|
-
if ((afterBalance - beforeBalance) != orderSize) {
|
|
474
|
-
revert ERC20TransferAmountMismatch();
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
IERC20(currency).approve(swapRouter, trueOrderSize);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/// @dev Handles sending ETH and ERC20 payouts and refunds to recipients
|
|
482
|
-
/// @param orderPayout The amount of currency to pay out
|
|
483
|
-
/// @param recipient The address to receive the payout
|
|
484
|
-
function _handlePayout(uint256 orderPayout, address recipient) internal {
|
|
485
|
-
if (currency == WETH) {
|
|
486
|
-
Address.sendValue(payable(recipient), orderPayout);
|
|
487
|
-
} else {
|
|
488
|
-
IERC20(currency).safeTransfer(recipient, orderPayout);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/// @dev Handles calculating and depositing fees to an escrow protocol rewards contract
|
|
493
|
-
function _handleTradeRewards(uint256 totalValue, address _tradeReferrer) internal {
|
|
494
|
-
if (_tradeReferrer == address(0)) {
|
|
495
|
-
_tradeReferrer = protocolRewardRecipient;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
uint256 tokenCreatorFee = _calculateReward(totalValue, TOKEN_CREATOR_FEE_BPS);
|
|
499
|
-
uint256 platformReferrerFee = _calculateReward(totalValue, PLATFORM_REFERRER_FEE_BPS);
|
|
500
|
-
uint256 tradeReferrerFee = _calculateReward(totalValue, TRADE_REFERRER_FEE_BPS);
|
|
501
|
-
uint256 protocolFee = totalValue - tokenCreatorFee - platformReferrerFee - tradeReferrerFee;
|
|
502
|
-
|
|
503
|
-
if (currency == WETH) {
|
|
504
|
-
address[] memory recipients = new address[](4);
|
|
505
|
-
uint256[] memory amounts = new uint256[](4);
|
|
506
|
-
bytes4[] memory reasons = new bytes4[](4);
|
|
507
|
-
|
|
508
|
-
recipients[0] = payoutRecipient;
|
|
509
|
-
amounts[0] = tokenCreatorFee;
|
|
510
|
-
reasons[0] = bytes4(keccak256("COIN_CREATOR_REWARD"));
|
|
511
|
-
|
|
512
|
-
recipients[1] = platformReferrer;
|
|
513
|
-
amounts[1] = platformReferrerFee;
|
|
514
|
-
reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_REWARD"));
|
|
515
|
-
|
|
516
|
-
recipients[2] = _tradeReferrer;
|
|
517
|
-
amounts[2] = tradeReferrerFee;
|
|
518
|
-
reasons[2] = bytes4(keccak256("COIN_TRADE_REFERRER_REWARD"));
|
|
519
|
-
|
|
520
|
-
recipients[3] = protocolRewardRecipient;
|
|
521
|
-
amounts[3] = protocolFee;
|
|
522
|
-
reasons[3] = bytes4(keccak256("COIN_PROTOCOL_REWARD"));
|
|
523
|
-
|
|
524
|
-
IProtocolRewards(protocolRewards).depositBatch{value: totalValue}(recipients, amounts, reasons, "");
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
if (currency != WETH) {
|
|
528
|
-
IERC20(currency).safeTransfer(payoutRecipient, tokenCreatorFee);
|
|
529
|
-
IERC20(currency).safeTransfer(platformReferrer, platformReferrerFee);
|
|
530
|
-
IERC20(currency).safeTransfer(_tradeReferrer, tradeReferrerFee);
|
|
531
|
-
IERC20(currency).safeTransfer(protocolRewardRecipient, protocolFee);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
emit CoinTradeRewards(
|
|
535
|
-
payoutRecipient,
|
|
536
|
-
platformReferrer,
|
|
537
|
-
_tradeReferrer,
|
|
538
|
-
protocolRewardRecipient,
|
|
539
|
-
tokenCreatorFee,
|
|
540
|
-
platformReferrerFee,
|
|
541
|
-
tradeReferrerFee,
|
|
542
|
-
protocolFee,
|
|
543
|
-
currency
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/// @dev Collects and distributes accrued fees from all LP positions
|
|
548
|
-
function _handleMarketRewards() internal returns (MarketRewards memory) {
|
|
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
|
-
}
|
|
572
|
-
|
|
573
|
-
address token0 = currency < address(this) ? currency : address(this);
|
|
574
|
-
address token1 = currency < address(this) ? address(this) : currency;
|
|
575
|
-
|
|
576
|
-
MarketRewards memory rewards;
|
|
577
|
-
|
|
578
|
-
rewards = _transferMarketRewards(token0, totalAmountToken0, rewards);
|
|
579
|
-
rewards = _transferMarketRewards(token1, totalAmountToken1, rewards);
|
|
580
|
-
|
|
581
|
-
emit CoinMarketRewards(payoutRecipient, platformReferrer, protocolRewardRecipient, currency, rewards);
|
|
582
|
-
|
|
583
|
-
return rewards;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function _transferMarketRewards(address token, uint256 totalAmount, MarketRewards memory rewards) internal returns (MarketRewards memory) {
|
|
587
|
-
if (totalAmount > 0) {
|
|
588
|
-
address dopplerRecipient = IAirlock(airlock).owner();
|
|
589
|
-
uint256 dopplerPayout = _calculateReward(totalAmount, DOPPLER_MARKET_REWARD_BPS);
|
|
590
|
-
uint256 creatorPayout = _calculateReward(totalAmount, CREATOR_MARKET_REWARD_BPS);
|
|
591
|
-
uint256 platformReferrerPayout = _calculateReward(totalAmount, PLATFORM_REFERRER_MARKET_REWARD_BPS);
|
|
592
|
-
uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout - dopplerPayout;
|
|
593
|
-
|
|
594
|
-
if (token == WETH) {
|
|
595
|
-
IWETH(WETH).withdraw(totalAmount);
|
|
596
|
-
|
|
597
|
-
rewards.totalAmountCurrency = totalAmount;
|
|
598
|
-
rewards.creatorPayoutAmountCurrency = creatorPayout;
|
|
599
|
-
rewards.platformReferrerAmountCurrency = platformReferrerPayout;
|
|
600
|
-
rewards.protocolAmountCurrency = protocolPayout;
|
|
601
|
-
|
|
602
|
-
address[] memory recipients = new address[](4);
|
|
603
|
-
recipients[0] = payoutRecipient;
|
|
604
|
-
recipients[1] = platformReferrer;
|
|
605
|
-
recipients[2] = protocolRewardRecipient;
|
|
606
|
-
recipients[3] = dopplerRecipient;
|
|
607
|
-
|
|
608
|
-
uint256[] memory amounts = new uint256[](4);
|
|
609
|
-
amounts[0] = rewards.creatorPayoutAmountCurrency;
|
|
610
|
-
amounts[1] = rewards.platformReferrerAmountCurrency;
|
|
611
|
-
amounts[2] = rewards.protocolAmountCurrency;
|
|
612
|
-
amounts[3] = dopplerPayout;
|
|
613
|
-
|
|
614
|
-
bytes4[] memory reasons = new bytes4[](4);
|
|
615
|
-
reasons[0] = bytes4(keccak256("COIN_CREATOR_MARKET_REWARD"));
|
|
616
|
-
reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_MARKET_REWARD"));
|
|
617
|
-
reasons[2] = bytes4(keccak256("COIN_PROTOCOL_MARKET_REWARD"));
|
|
618
|
-
reasons[3] = bytes4(keccak256("COIN_DOPPLER_MARKET_REWARD"));
|
|
619
|
-
|
|
620
|
-
IProtocolRewards(protocolRewards).depositBatch{value: totalAmount}(recipients, amounts, reasons, "");
|
|
621
|
-
IProtocolRewards(protocolRewards).withdrawFor(dopplerRecipient, dopplerPayout);
|
|
622
|
-
} else if (token == address(this)) {
|
|
623
|
-
rewards.totalAmountCoin = totalAmount;
|
|
624
|
-
rewards.creatorPayoutAmountCoin = creatorPayout;
|
|
625
|
-
rewards.platformReferrerAmountCoin = platformReferrerPayout;
|
|
626
|
-
rewards.protocolAmountCoin = protocolPayout;
|
|
627
|
-
|
|
628
|
-
_transfer(address(this), payoutRecipient, rewards.creatorPayoutAmountCoin);
|
|
629
|
-
_transfer(address(this), platformReferrer, rewards.platformReferrerAmountCoin);
|
|
630
|
-
_transfer(address(this), protocolRewardRecipient, rewards.protocolAmountCoin);
|
|
631
|
-
_transfer(address(this), dopplerRecipient, dopplerPayout);
|
|
632
|
-
} else {
|
|
633
|
-
rewards.totalAmountCurrency = totalAmount;
|
|
634
|
-
rewards.creatorPayoutAmountCurrency = creatorPayout;
|
|
635
|
-
rewards.platformReferrerAmountCurrency = platformReferrerPayout;
|
|
636
|
-
rewards.protocolAmountCurrency = protocolPayout;
|
|
637
|
-
|
|
638
|
-
IERC20(currency).safeTransfer(payoutRecipient, creatorPayout);
|
|
639
|
-
IERC20(currency).safeTransfer(platformReferrer, platformReferrerPayout);
|
|
640
|
-
IERC20(currency).safeTransfer(protocolRewardRecipient, protocolPayout);
|
|
641
|
-
IERC20(currency).safeTransfer(dopplerRecipient, dopplerPayout);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return rewards;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/// @dev Utility for computing amounts in basis points.
|
|
649
|
-
function _calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
|
|
650
|
-
return (amount * bps) / 10_000;
|
|
651
|
-
}
|
|
652
345
|
}
|
package/src/ZoraFactoryImpl.sol
CHANGED
|
@@ -9,11 +9,17 @@ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.s
|
|
|
9
9
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
10
10
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
11
11
|
import {CoinConfigurationVersions} from "./libs/CoinConfigurationVersions.sol";
|
|
12
|
-
|
|
12
|
+
import {ISwapRouter} from "./interfaces/ISwapRouter.sol";
|
|
13
|
+
import {IWETH} from "./interfaces/IWETH.sol";
|
|
13
14
|
import {IZoraFactory} from "./interfaces/IZoraFactory.sol";
|
|
15
|
+
import {IHasAfterCoinDeploy} from "./hooks/BaseCoinDeployHook.sol";
|
|
16
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
14
17
|
import {Coin} from "./Coin.sol";
|
|
18
|
+
import {ICoin} from "./interfaces/ICoin.sol";
|
|
19
|
+
import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
|
|
20
|
+
import {ContractVersionBase} from "./version/ContractVersionBase.sol";
|
|
15
21
|
|
|
16
|
-
contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable {
|
|
22
|
+
contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable, IHasContractName, ContractVersionBase {
|
|
17
23
|
using SafeERC20 for IERC20;
|
|
18
24
|
|
|
19
25
|
/// @notice The coin contract implementation address
|
|
@@ -23,15 +29,7 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
|
|
|
23
29
|
coinImpl = _coinImpl;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
/// @
|
|
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
|
|
32
|
+
/// @inheritdoc IZoraFactory
|
|
35
33
|
function deploy(
|
|
36
34
|
address payoutRecipient,
|
|
37
35
|
address[] memory owners,
|
|
@@ -42,40 +40,39 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
|
|
|
42
40
|
address platformReferrer,
|
|
43
41
|
uint256 orderSize
|
|
44
42
|
) public payable nonReentrant returns (address, uint256) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Coin coin = Coin(payable(Clones.cloneDeterministic(coinImpl, salt)));
|
|
48
|
-
|
|
49
|
-
coin.initialize(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
|
|
43
|
+
Coin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
|
|
50
44
|
|
|
51
45
|
uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
|
|
52
46
|
|
|
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
47
|
return (address(coin), coinsPurchased);
|
|
67
48
|
}
|
|
68
49
|
|
|
69
|
-
/// @
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
50
|
+
/// @inheritdoc IZoraFactory
|
|
51
|
+
function deployWithHook(
|
|
52
|
+
address payoutRecipient,
|
|
53
|
+
address[] memory owners,
|
|
54
|
+
string memory uri,
|
|
55
|
+
string memory name,
|
|
56
|
+
string memory symbol,
|
|
57
|
+
bytes memory poolConfig,
|
|
58
|
+
address platformReferrer,
|
|
59
|
+
address hook,
|
|
60
|
+
bytes calldata hookData
|
|
61
|
+
) public payable nonReentrant returns (address coin, bytes memory hookDataOut) {
|
|
62
|
+
coin = address(_createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer));
|
|
63
|
+
|
|
64
|
+
if (hook != address(0)) {
|
|
65
|
+
if (!IERC165(hook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
|
|
66
|
+
revert InvalidHook();
|
|
67
|
+
}
|
|
68
|
+
hookDataOut = IHasAfterCoinDeploy(hook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
|
|
69
|
+
} else if (msg.value > 0) {
|
|
70
|
+
// cannot send eth without a hook
|
|
71
|
+
revert EthTransferInvalid();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// @inheritdoc IZoraFactory
|
|
79
76
|
function deploy(
|
|
80
77
|
address payoutRecipient,
|
|
81
78
|
address[] memory owners,
|
|
@@ -87,16 +84,30 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
|
|
|
87
84
|
int24 tickLower,
|
|
88
85
|
uint256 orderSize
|
|
89
86
|
) public payable nonReentrant returns (address, uint256) {
|
|
87
|
+
bytes memory poolConfig = abi.encode(CoinConfigurationVersions.LEGACY_POOL_VERSION, currency, tickLower);
|
|
88
|
+
|
|
89
|
+
Coin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
|
|
90
|
+
|
|
91
|
+
uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
|
|
92
|
+
|
|
93
|
+
return (address(coin), coinsPurchased);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function _createAndInitializeCoin(
|
|
97
|
+
address payoutRecipient,
|
|
98
|
+
address[] memory owners,
|
|
99
|
+
string memory uri,
|
|
100
|
+
string memory name,
|
|
101
|
+
string memory symbol,
|
|
102
|
+
bytes memory poolConfig,
|
|
103
|
+
address platformReferrer
|
|
104
|
+
) internal returns (Coin) {
|
|
90
105
|
bytes32 salt = _generateSalt(payoutRecipient, uri);
|
|
91
106
|
|
|
92
107
|
Coin coin = Coin(payable(Clones.cloneDeterministic(coinImpl, salt)));
|
|
93
108
|
|
|
94
|
-
bytes memory poolConfig = abi.encode(CoinConfigurationVersions.LEGACY_POOL_VERSION, currency, tickLower);
|
|
95
|
-
|
|
96
109
|
coin.initialize(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
|
|
97
110
|
|
|
98
|
-
uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
|
|
99
|
-
|
|
100
111
|
emit CoinCreated(
|
|
101
112
|
msg.sender,
|
|
102
113
|
payoutRecipient,
|
|
@@ -110,7 +121,7 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
|
|
|
110
121
|
coin.contractVersion()
|
|
111
122
|
);
|
|
112
123
|
|
|
113
|
-
return
|
|
124
|
+
return coin;
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
/// @dev Generates a unique salt for deterministic deployment
|
|
@@ -182,7 +193,24 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
|
|
|
182
193
|
return ERC1967Utils.getImplementation();
|
|
183
194
|
}
|
|
184
195
|
|
|
196
|
+
/// @inheritdoc IHasContractName
|
|
197
|
+
function contractName() public pure override returns (string memory) {
|
|
198
|
+
return "ZoraCoinFactory";
|
|
199
|
+
}
|
|
200
|
+
|
|
185
201
|
/// @dev Authorizes an upgrade to a new implementation
|
|
186
202
|
/// @param newImpl The new implementation address
|
|
187
|
-
function _authorizeUpgrade(address newImpl) internal override onlyOwner {
|
|
203
|
+
function _authorizeUpgrade(address newImpl) internal override onlyOwner {
|
|
204
|
+
// try to get the existing contract name - if it reverts, the existing contract was an older version that didn't have the contract name
|
|
205
|
+
// unfortunately we cannot use supportsInterface here because the existing implementation did not have that function
|
|
206
|
+
try IHasContractName(newImpl).contractName() returns (string memory name) {
|
|
207
|
+
if (!_equals(name, contractName())) {
|
|
208
|
+
revert UpgradeToMismatchedContractName(contractName(), name);
|
|
209
|
+
}
|
|
210
|
+
} catch {}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function _equals(string memory a, string memory b) internal pure returns (bool) {
|
|
214
|
+
return (keccak256(bytes(a)) == keccak256(bytes(b)));
|
|
215
|
+
}
|
|
188
216
|
}
|