@zoralabs/coins 0.9.0 → 1.0.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 +131 -114
- package/CHANGELOG.md +40 -0
- package/abis/BaseCoin.json +26 -118
- package/abis/BaseTest.json +47 -0
- package/abis/Coin.json +171 -63
- package/abis/CoinDopplerMultiCurve.json +38 -0
- package/abis/CoinRewardsV4.json +54 -0
- package/abis/CoinTest.json +53 -20
- package/abis/CoinUniV4Test.json +1053 -0
- package/abis/CoinV4.json +234 -211
- package/abis/DeployScript.json +47 -0
- package/abis/DeployedCoinVersionLookup.json +21 -0
- package/abis/DeployedCoinVersionLookupTest.json +716 -0
- package/abis/DifferentNamespaceVersionLookup.json +39 -0
- package/abis/DopplerUniswapV3Test.json +49 -93
- package/abis/ERC20.json +310 -0
- package/abis/FactoryTest.json +85 -7
- package/abis/FeeEstimatorHook.json +1528 -0
- package/abis/HooksDeployment.json +23 -0
- package/abis/HooksTest.json +47 -0
- package/abis/ICoin.json +40 -71
- package/abis/ICoinV3.json +879 -0
- package/abis/ICoinV4.json +915 -0
- package/abis/IDeployedCoinVersionLookup.json +21 -0
- package/abis/IERC721.json +36 -36
- package/abis/IHasPoolKey.json +42 -0
- package/abis/IHasRewardsRecipients.json +54 -0
- package/abis/IHasSwapPath.json +60 -0
- package/abis/IMsgSender.json +15 -0
- package/abis/IPoolConfigEncoding.json +46 -0
- package/abis/ISwapPathRouter.json +92 -0
- package/abis/IUniversalRouter.json +61 -0
- package/abis/IUnlockCallback.json +21 -0
- package/abis/IV4Quoter.json +310 -0
- package/abis/IZoraFactory.json +191 -11
- package/abis/IZoraV4CoinHook.json +348 -4
- package/abis/MockERC20.json +21 -0
- package/abis/MultiOwnableTest.json +47 -0
- package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
- package/abis/PrintUpgradeCommand.json +9 -0
- package/abis/ProxyShim.json +24 -0
- package/abis/StateLibrary.json +80 -0
- package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
- package/abis/TestV4Swap.json +9 -0
- package/abis/UpgradeCoinImpl.json +47 -0
- package/abis/UpgradesTest.json +67 -0
- package/abis/Vm.json +1482 -111
- package/abis/VmSafe.json +856 -32
- package/abis/ZoraFactoryImpl.json +339 -1
- package/abis/ZoraV4CoinHook.json +455 -5
- package/addresses/8453.json +8 -4
- package/addresses/84532.json +8 -4
- package/dist/index.cjs +1920 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1916 -169
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +2599 -183
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +1928 -165
- package/package.json +8 -3
- package/remappings.txt +6 -1
- package/script/CoinsDeployerBase.sol +74 -11
- package/script/DeployDevFactory.s.sol +21 -0
- package/script/PrintUpgradeCommand.s.sol +13 -0
- package/script/Simulate.s.sol +1 -10
- package/script/TestBackingCoinSwap.s.sol +146 -0
- package/script/TestV4Swap.s.sol +136 -0
- package/script/UpgradeFactoryImpl.s.sol +1 -1
- package/src/BaseCoin.sol +176 -0
- package/src/Coin.sol +87 -202
- package/src/CoinV4.sol +121 -0
- package/src/ZoraFactoryImpl.sol +208 -36
- package/src/hooks/ZoraV4CoinHook.sol +195 -0
- package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
- package/src/hooks/{BuySupplyWithSwapRouterHook.sol → deployment/BuySupplyWithSwapRouterHook.sol} +7 -5
- package/src/interfaces/ICoin.sol +31 -39
- package/src/interfaces/ICoinV3.sol +71 -0
- package/src/interfaces/ICoinV4.sol +69 -0
- package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
- package/src/interfaces/IMsgSender.sol +9 -0
- package/src/interfaces/IPoolConfigEncoding.sol +14 -0
- package/src/interfaces/ISwapPathRouter.sol +14 -0
- package/src/interfaces/IZoraFactory.sol +65 -27
- package/src/interfaces/IZoraV4CoinHook.sol +116 -0
- package/src/libs/CoinCommon.sol +15 -0
- package/src/libs/CoinConfigurationVersions.sol +116 -1
- package/src/libs/CoinConstants.sol +5 -0
- package/src/libs/CoinDopplerMultiCurve.sol +134 -0
- package/src/libs/CoinDopplerUniV3.sol +19 -171
- package/src/libs/CoinRewards.sol +195 -0
- package/src/libs/CoinRewardsV4.sol +180 -0
- package/src/libs/CoinSetup.sol +57 -0
- package/src/libs/CoinSetupV3.sol +6 -67
- package/src/libs/DopplerMath.sol +156 -0
- package/src/libs/HooksDeployment.sol +84 -0
- package/src/libs/MarketConstants.sol +4 -0
- package/src/libs/PoolStateReader.sol +22 -0
- package/src/libs/UniV3BuySell.sol +74 -292
- package/src/libs/UniV4SwapHelper.sol +65 -0
- package/src/libs/UniV4SwapToCurrency.sol +109 -0
- package/src/libs/V4Liquidity.sol +129 -0
- package/src/types/PoolConfiguration.sol +15 -0
- package/src/utils/DeployedCoinVersionLookup.sol +52 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Coin.t.sol +78 -88
- package/test/CoinDopplerUniV3.t.sol +32 -171
- package/test/CoinUniV4.t.sol +752 -0
- package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +2 -6
- package/test/Factory.t.sol +80 -47
- package/test/MultiOwnable.t.sol +6 -3
- package/test/Upgrades.t.sol +6 -5
- package/test/mocks/MockERC20.sol +12 -0
- package/test/utils/BaseTest.sol +106 -56
- package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
- package/test/utils/FeeEstimatorHook.sol +84 -0
- package/test/utils/ProxyShim.sol +17 -0
- package/wagmi.config.ts +4 -0
- package/.env +0 -1
- package/.turbo/turbo-update-contract-version.log +0 -22
- package/abis/CoinSetupV3.json +0 -7
- package/abis/HookDeployer.json +0 -68
- package/abis/IHookDeployer.json +0 -42
- package/src/libs/CoinLegacy.sol +0 -48
- package/src/libs/CoinLegacyMarket.sol +0 -182
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
5
|
+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
6
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
7
|
+
import {IProtocolRewards} from "../interfaces/IProtocolRewards.sol";
|
|
8
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
9
|
+
import {CoinConstants} from "./CoinConstants.sol";
|
|
10
|
+
import {IWETH} from "../interfaces/IWETH.sol";
|
|
11
|
+
|
|
12
|
+
struct CoinConfig {
|
|
13
|
+
address protocolRewardRecipient;
|
|
14
|
+
address platformReferrer;
|
|
15
|
+
address payoutRecipient;
|
|
16
|
+
address protocolRewards;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
library CoinRewards {
|
|
20
|
+
using SafeERC20 for IERC20;
|
|
21
|
+
|
|
22
|
+
/// @dev Handles sending ETH and ERC20 payouts and refunds to recipients
|
|
23
|
+
/// @param orderPayout The amount of currency to pay out
|
|
24
|
+
/// @param recipient The address to receive the payout
|
|
25
|
+
function handlePayout(uint256 orderPayout, address recipient, address currency, address weth) internal {
|
|
26
|
+
if (currency == weth) {
|
|
27
|
+
Address.sendValue(payable(recipient), orderPayout);
|
|
28
|
+
} else {
|
|
29
|
+
IERC20(currency).safeTransfer(recipient, orderPayout);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// @dev Handles calculating and depositing fees to an escrow protocol rewards contract
|
|
34
|
+
function handleTradeRewards(uint256 totalValue, address _tradeReferrer, CoinConfig memory coinConfig, address currency, IWETH weth) internal {
|
|
35
|
+
address protocolRewardRecipient = coinConfig.protocolRewardRecipient;
|
|
36
|
+
address platformReferrer = coinConfig.platformReferrer;
|
|
37
|
+
address payoutRecipient = coinConfig.payoutRecipient;
|
|
38
|
+
IProtocolRewards protocolRewards = IProtocolRewards(coinConfig.protocolRewards);
|
|
39
|
+
|
|
40
|
+
if (_tradeReferrer == address(0)) {
|
|
41
|
+
_tradeReferrer = protocolRewardRecipient;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
uint256 tokenCreatorFee = calculateReward(totalValue, CoinConstants.TOKEN_CREATOR_FEE_BPS);
|
|
45
|
+
uint256 platformReferrerFee = calculateReward(totalValue, CoinConstants.PLATFORM_REFERRER_FEE_BPS);
|
|
46
|
+
uint256 tradeReferrerFee = calculateReward(totalValue, CoinConstants.TRADE_REFERRER_FEE_BPS);
|
|
47
|
+
uint256 protocolFee = totalValue - tokenCreatorFee - platformReferrerFee - tradeReferrerFee;
|
|
48
|
+
|
|
49
|
+
if (currency == address(weth)) {
|
|
50
|
+
address[] memory recipients = new address[](4);
|
|
51
|
+
uint256[] memory amounts = new uint256[](4);
|
|
52
|
+
bytes4[] memory reasons = new bytes4[](4);
|
|
53
|
+
|
|
54
|
+
recipients[0] = payoutRecipient;
|
|
55
|
+
amounts[0] = tokenCreatorFee;
|
|
56
|
+
reasons[0] = bytes4(keccak256("COIN_CREATOR_REWARD"));
|
|
57
|
+
|
|
58
|
+
recipients[1] = platformReferrer;
|
|
59
|
+
amounts[1] = platformReferrerFee;
|
|
60
|
+
reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_REWARD"));
|
|
61
|
+
|
|
62
|
+
recipients[2] = _tradeReferrer;
|
|
63
|
+
amounts[2] = tradeReferrerFee;
|
|
64
|
+
reasons[2] = bytes4(keccak256("COIN_TRADE_REFERRER_REWARD"));
|
|
65
|
+
|
|
66
|
+
recipients[3] = protocolRewardRecipient;
|
|
67
|
+
amounts[3] = protocolFee;
|
|
68
|
+
reasons[3] = bytes4(keccak256("COIN_PROTOCOL_REWARD"));
|
|
69
|
+
|
|
70
|
+
IProtocolRewards(protocolRewards).depositBatch{value: totalValue}(recipients, amounts, reasons, "");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (currency != address(weth)) {
|
|
74
|
+
IERC20(currency).safeTransfer(payoutRecipient, tokenCreatorFee);
|
|
75
|
+
IERC20(currency).safeTransfer(platformReferrer, platformReferrerFee);
|
|
76
|
+
IERC20(currency).safeTransfer(_tradeReferrer, tradeReferrerFee);
|
|
77
|
+
IERC20(currency).safeTransfer(protocolRewardRecipient, protocolFee);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
emit ICoin.CoinTradeRewards(
|
|
81
|
+
payoutRecipient,
|
|
82
|
+
platformReferrer,
|
|
83
|
+
_tradeReferrer,
|
|
84
|
+
protocolRewardRecipient,
|
|
85
|
+
tokenCreatorFee,
|
|
86
|
+
platformReferrerFee,
|
|
87
|
+
tradeReferrerFee,
|
|
88
|
+
protocolFee,
|
|
89
|
+
currency
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
|
|
94
|
+
return (amount * bps) / 10_000;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function transferBothRewards(
|
|
98
|
+
address token0,
|
|
99
|
+
uint256 totalAmountToken0,
|
|
100
|
+
address token1,
|
|
101
|
+
uint256 totalAmountToken1,
|
|
102
|
+
address coin,
|
|
103
|
+
CoinConfig memory coinConfig,
|
|
104
|
+
address currency,
|
|
105
|
+
IWETH weth,
|
|
106
|
+
address doppler
|
|
107
|
+
) internal returns (ICoin.MarketRewards memory rewards) {
|
|
108
|
+
rewards = transferMarketRewards(token0, currency, totalAmountToken0, rewards, coin, coinConfig, weth, doppler);
|
|
109
|
+
rewards = transferMarketRewards(token1, currency, totalAmountToken1, rewards, coin, coinConfig, weth, doppler);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
struct Distribution {
|
|
113
|
+
bool isCurrency;
|
|
114
|
+
uint256 totalAmount;
|
|
115
|
+
uint256 creatorPayout;
|
|
116
|
+
uint256 platformReferrerPayout;
|
|
117
|
+
uint256 protocolPayout;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function transferMarketRewards(
|
|
121
|
+
address token,
|
|
122
|
+
address currency,
|
|
123
|
+
uint256 totalAmount,
|
|
124
|
+
ICoin.MarketRewards memory rewards,
|
|
125
|
+
address coin,
|
|
126
|
+
CoinConfig memory coinConfig,
|
|
127
|
+
IWETH weth,
|
|
128
|
+
address dopplerRecipient
|
|
129
|
+
) internal returns (ICoin.MarketRewards memory) {
|
|
130
|
+
address payoutRecipient = coinConfig.payoutRecipient;
|
|
131
|
+
address platformReferrer = coinConfig.platformReferrer;
|
|
132
|
+
address protocolRewardRecipient = coinConfig.protocolRewardRecipient;
|
|
133
|
+
address protocolRewards = coinConfig.protocolRewards;
|
|
134
|
+
|
|
135
|
+
if (totalAmount > 0) {
|
|
136
|
+
uint256 dopplerPayout = calculateReward(totalAmount, CoinConstants.DOPPLER_MARKET_REWARD_BPS);
|
|
137
|
+
uint256 creatorPayout = calculateReward(totalAmount, CoinConstants.CREATOR_MARKET_REWARD_BPS);
|
|
138
|
+
uint256 platformReferrerPayout = calculateReward(totalAmount, CoinConstants.PLATFORM_REFERRER_MARKET_REWARD_BPS);
|
|
139
|
+
uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout - dopplerPayout;
|
|
140
|
+
|
|
141
|
+
bool isCurrency = token == currency;
|
|
142
|
+
|
|
143
|
+
if (token == address(weth)) {
|
|
144
|
+
IWETH(weth).withdraw(totalAmount);
|
|
145
|
+
|
|
146
|
+
address[] memory recipients = new address[](4);
|
|
147
|
+
recipients[0] = payoutRecipient;
|
|
148
|
+
recipients[1] = platformReferrer;
|
|
149
|
+
recipients[2] = protocolRewardRecipient;
|
|
150
|
+
recipients[3] = dopplerRecipient;
|
|
151
|
+
|
|
152
|
+
uint256[] memory amounts = new uint256[](4);
|
|
153
|
+
amounts[0] = creatorPayout;
|
|
154
|
+
amounts[1] = platformReferrerPayout;
|
|
155
|
+
amounts[2] = protocolPayout;
|
|
156
|
+
amounts[3] = dopplerPayout;
|
|
157
|
+
|
|
158
|
+
bytes4[] memory reasons = new bytes4[](4);
|
|
159
|
+
reasons[0] = bytes4(keccak256("COIN_CREATOR_MARKET_REWARD"));
|
|
160
|
+
reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_MARKET_REWARD"));
|
|
161
|
+
reasons[2] = bytes4(keccak256("COIN_PROTOCOL_MARKET_REWARD"));
|
|
162
|
+
reasons[3] = bytes4(keccak256("COIN_DOPPLER_MARKET_REWARD"));
|
|
163
|
+
|
|
164
|
+
IProtocolRewards(protocolRewards).depositBatch{value: totalAmount}(recipients, amounts, reasons, "");
|
|
165
|
+
IProtocolRewards(protocolRewards).withdrawFor(dopplerRecipient, dopplerPayout);
|
|
166
|
+
} else {
|
|
167
|
+
if (!isCurrency) {
|
|
168
|
+
IERC20(coin).safeTransfer(payoutRecipient, creatorPayout);
|
|
169
|
+
IERC20(coin).safeTransfer(platformReferrer, platformReferrerPayout);
|
|
170
|
+
IERC20(coin).safeTransfer(protocolRewardRecipient, protocolPayout);
|
|
171
|
+
IERC20(coin).safeTransfer(dopplerRecipient, dopplerPayout);
|
|
172
|
+
} else {
|
|
173
|
+
IERC20(currency).safeTransfer(payoutRecipient, creatorPayout);
|
|
174
|
+
IERC20(currency).safeTransfer(platformReferrer, platformReferrerPayout);
|
|
175
|
+
IERC20(currency).safeTransfer(protocolRewardRecipient, protocolPayout);
|
|
176
|
+
IERC20(currency).safeTransfer(dopplerRecipient, dopplerPayout);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (isCurrency) {
|
|
181
|
+
rewards.totalAmountCurrency = totalAmount;
|
|
182
|
+
rewards.creatorPayoutAmountCurrency = creatorPayout;
|
|
183
|
+
rewards.platformReferrerAmountCurrency = platformReferrerPayout;
|
|
184
|
+
rewards.protocolAmountCurrency = protocolPayout;
|
|
185
|
+
} else {
|
|
186
|
+
rewards.totalAmountCoin = totalAmount;
|
|
187
|
+
rewards.creatorPayoutAmountCoin = creatorPayout;
|
|
188
|
+
rewards.platformReferrerAmountCoin = platformReferrerPayout;
|
|
189
|
+
rewards.protocolAmountCoin = protocolPayout;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return rewards;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
5
|
+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
6
|
+
import {LpPosition} from "../types/LpPosition.sol";
|
|
7
|
+
import {V4Liquidity} from "./V4Liquidity.sol";
|
|
8
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
9
|
+
import {IHasRewardsRecipients} from "../interfaces/ICoin.sol";
|
|
10
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
11
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
12
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
13
|
+
import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
|
|
14
|
+
import {UniV4SwapToCurrency} from "./UniV4SwapToCurrency.sol";
|
|
15
|
+
import {IHasSwapPath} from "../interfaces/ICoinV4.sol";
|
|
16
|
+
|
|
17
|
+
library CoinRewardsV4 {
|
|
18
|
+
using SafeERC20 for IERC20;
|
|
19
|
+
|
|
20
|
+
// creator gets 50% of the total fee
|
|
21
|
+
uint256 public constant CREATOR_REWARD_BPS = 5000;
|
|
22
|
+
|
|
23
|
+
// create referrer gets 15% of the total fee
|
|
24
|
+
uint256 public constant CREATE_REFERRAL_REWARD_BPS = 1500;
|
|
25
|
+
|
|
26
|
+
// trade referrer gets 10% of the total fee
|
|
27
|
+
uint256 public constant TRADE_REFERRAL_REWARD_BPS = 1500;
|
|
28
|
+
|
|
29
|
+
// doppler gets 5% of the total fee
|
|
30
|
+
uint256 public constant DOPPLER_REWARD_BPS = 500;
|
|
31
|
+
|
|
32
|
+
function getTradeReferral(bytes calldata hookData) internal pure returns (address) {
|
|
33
|
+
return hookData.length > 0 ? abi.decode(hookData, (address)) : address(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// @notice Collects fees from LP positions, swaps them to target payout currency, and transfers to hook contract, so
|
|
37
|
+
/// that they can later be distributed as rewards.
|
|
38
|
+
/// @param poolManager The pool manager instance
|
|
39
|
+
/// @param key The pool key
|
|
40
|
+
/// @param positions The LP positions to collect fees from
|
|
41
|
+
/// @param payoutSwapPath The swap path to convert fees to target currency
|
|
42
|
+
/// @return fees0 The amount of fees collected in currency0
|
|
43
|
+
/// @return fees1 The amount of fees collected in currency1
|
|
44
|
+
/// @return receivedCurrency The final currency after swapping
|
|
45
|
+
/// @return receivedAmount The final amount after swapping
|
|
46
|
+
function collectFeesAndConvertToPayout(
|
|
47
|
+
IPoolManager poolManager,
|
|
48
|
+
PoolKey memory key,
|
|
49
|
+
LpPosition[] storage positions,
|
|
50
|
+
IHasSwapPath.PayoutSwapPath memory payoutSwapPath
|
|
51
|
+
) internal returns (int128 fees0, int128 fees1, Currency receivedCurrency, uint128 receivedAmount) {
|
|
52
|
+
// Step 1: Collect accrued fees from all LP positions in both token0 and token1
|
|
53
|
+
(fees0, fees1) = V4Liquidity.collectFees(poolManager, key, positions);
|
|
54
|
+
|
|
55
|
+
// Step 2: Swap the collected fees through the specified path to convert them to the target payout currency
|
|
56
|
+
// This handles multi-hop swaps if needed (e.g. coin -> backingCoin -> backingCoin's currency)
|
|
57
|
+
(receivedCurrency, receivedAmount) = UniV4SwapToCurrency.swapToPath(
|
|
58
|
+
poolManager,
|
|
59
|
+
uint128(fees0),
|
|
60
|
+
uint128(fees1),
|
|
61
|
+
payoutSwapPath.currencyIn,
|
|
62
|
+
payoutSwapPath.path
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Step 3: Transfer the final converted currency amount to this contract for distribution
|
|
66
|
+
// This makes the tokens available for the subsequent reward distribution
|
|
67
|
+
if (receivedAmount > 0) {
|
|
68
|
+
poolManager.take(receivedCurrency, address(this), receivedAmount);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// @notice Distributes collected market fees as rewards to various recipients including creator, referrers, protocol, and doppler
|
|
73
|
+
/// @dev Calculates reward amounts based on predefined basis points and transfers the specified currency to each recipient
|
|
74
|
+
/// @param currency The currency token to distribute as rewards (can be native ETH if address is zero)
|
|
75
|
+
/// @param fees The total amount of fees collected to be distributed
|
|
76
|
+
/// @param coin The coin contract instance that implements IHasRewardsRecipients to get recipient addresses
|
|
77
|
+
/// @param tradeReferrer The address of the trade referrer who should receive trade referral rewards (can be zero address)
|
|
78
|
+
function distributeMarketRewards(Currency currency, uint128 fees, IHasRewardsRecipients coin, address tradeReferrer) internal {
|
|
79
|
+
address payoutRecipient = coin.payoutRecipient();
|
|
80
|
+
address platformReferrer = coin.platformReferrer();
|
|
81
|
+
address protocolRewardRecipient = coin.protocolRewardRecipient();
|
|
82
|
+
address doppler = coin.dopplerFeeRecipient();
|
|
83
|
+
|
|
84
|
+
MarketRewards memory rewards = _distributeCurrencyRewards(
|
|
85
|
+
currency,
|
|
86
|
+
fees,
|
|
87
|
+
payoutRecipient,
|
|
88
|
+
platformReferrer,
|
|
89
|
+
protocolRewardRecipient,
|
|
90
|
+
doppler,
|
|
91
|
+
tradeReferrer
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
IZoraV4CoinHook.MarketRewardsV4 memory marketRewards = IZoraV4CoinHook.MarketRewardsV4({
|
|
95
|
+
creatorPayoutAmountCurrency: rewards.creatorAmount,
|
|
96
|
+
creatorPayoutAmountCoin: 0,
|
|
97
|
+
platformReferrerAmountCurrency: rewards.platformReferrerAmount,
|
|
98
|
+
platformReferrerAmountCoin: 0,
|
|
99
|
+
tradeReferrerAmountCurrency: rewards.tradeReferrerAmount,
|
|
100
|
+
tradeReferrerAmountCoin: 0,
|
|
101
|
+
protocolAmountCurrency: rewards.protocolAmount,
|
|
102
|
+
protocolAmountCoin: 0,
|
|
103
|
+
dopplerAmountCurrency: rewards.dopplerAmount,
|
|
104
|
+
dopplerAmountCoin: 0
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
emit IZoraV4CoinHook.CoinMarketRewardsV4(
|
|
108
|
+
address(coin),
|
|
109
|
+
Currency.unwrap(currency),
|
|
110
|
+
payoutRecipient,
|
|
111
|
+
platformReferrer,
|
|
112
|
+
tradeReferrer,
|
|
113
|
+
protocolRewardRecipient,
|
|
114
|
+
doppler,
|
|
115
|
+
marketRewards
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
struct MarketRewards {
|
|
120
|
+
uint256 platformReferrerAmount;
|
|
121
|
+
uint256 tradeReferrerAmount;
|
|
122
|
+
uint256 protocolAmount;
|
|
123
|
+
uint256 creatorAmount;
|
|
124
|
+
uint256 dopplerAmount;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function _distributeCurrencyRewards(
|
|
128
|
+
Currency currency,
|
|
129
|
+
uint128 fee,
|
|
130
|
+
address payoutRecipient,
|
|
131
|
+
address platformReferrer,
|
|
132
|
+
address protocolRewardRecipient,
|
|
133
|
+
address doppler,
|
|
134
|
+
address tradeReferral
|
|
135
|
+
) internal returns (MarketRewards memory rewards) {
|
|
136
|
+
rewards = _computeMarketRewards(fee, tradeReferral != address(0), platformReferrer != address(0));
|
|
137
|
+
|
|
138
|
+
if (platformReferrer != address(0)) {
|
|
139
|
+
_transferCurrency(currency, rewards.platformReferrerAmount, platformReferrer);
|
|
140
|
+
}
|
|
141
|
+
if (tradeReferral != address(0)) {
|
|
142
|
+
_transferCurrency(currency, rewards.tradeReferrerAmount, tradeReferral);
|
|
143
|
+
}
|
|
144
|
+
_transferCurrency(currency, rewards.creatorAmount, payoutRecipient);
|
|
145
|
+
_transferCurrency(currency, rewards.dopplerAmount, doppler);
|
|
146
|
+
_transferCurrency(currency, rewards.protocolAmount, protocolRewardRecipient);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function _transferCurrency(Currency currency, uint256 amount, address to) internal {
|
|
150
|
+
if (amount == 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (currency.isAddressZero()) {
|
|
155
|
+
(bool success, ) = payable(to).call{value: amount}("");
|
|
156
|
+
if (!success) {
|
|
157
|
+
revert ICoin.EthTransferFailed();
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
IERC20(Currency.unwrap(currency)).safeTransfer(to, amount);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function _computeMarketRewards(uint128 fee, bool hasTradeReferral, bool hasCreateReferral) internal pure returns (MarketRewards memory rewards) {
|
|
165
|
+
if (fee == 0) {
|
|
166
|
+
return rewards;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
uint256 totalAmount = uint256(fee);
|
|
170
|
+
rewards.platformReferrerAmount = hasCreateReferral ? calculateReward(totalAmount, CREATE_REFERRAL_REWARD_BPS) : 0;
|
|
171
|
+
rewards.tradeReferrerAmount = hasTradeReferral ? calculateReward(totalAmount, TRADE_REFERRAL_REWARD_BPS) : 0;
|
|
172
|
+
rewards.creatorAmount = calculateReward(totalAmount, CREATOR_REWARD_BPS);
|
|
173
|
+
rewards.dopplerAmount = calculateReward(totalAmount, DOPPLER_REWARD_BPS);
|
|
174
|
+
rewards.protocolAmount = totalAmount - rewards.platformReferrerAmount - rewards.tradeReferrerAmount - rewards.creatorAmount - rewards.dopplerAmount;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
|
|
178
|
+
return (amount * bps) / 10_000;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {PoolConfigurationV4} from "../interfaces/ICoin.sol";
|
|
5
|
+
import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
|
|
6
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
7
|
+
import {CoinCommon} from "./CoinCommon.sol";
|
|
8
|
+
import {MarketConstants} from "./MarketConstants.sol";
|
|
9
|
+
import {TickMath} from "../utils/uniswap/TickMath.sol";
|
|
10
|
+
import {IPoolManager, PoolKey, Currency, IHooks} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
11
|
+
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
|
|
12
|
+
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
|
|
13
|
+
import {MarketConstants} from "./MarketConstants.sol";
|
|
14
|
+
import {LpPosition} from "../types/LpPosition.sol";
|
|
15
|
+
import {CoinDopplerMultiCurve, PoolConfiguration} from "./CoinDopplerMultiCurve.sol";
|
|
16
|
+
import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
|
|
17
|
+
|
|
18
|
+
library CoinSetup {
|
|
19
|
+
function generatePoolConfig(
|
|
20
|
+
address coin,
|
|
21
|
+
bytes memory poolConfig_
|
|
22
|
+
) internal pure returns (uint8 version, address currency, uint160 sqrtPriceX96, bool isCoinToken0, PoolConfiguration memory poolConfiguration) {
|
|
23
|
+
// Extract version and currency from pool config
|
|
24
|
+
(version, currency) = CoinConfigurationVersions.decodeVersionAndCurrency(poolConfig_);
|
|
25
|
+
|
|
26
|
+
isCoinToken0 = CoinCommon.sortTokens(coin, currency);
|
|
27
|
+
|
|
28
|
+
(sqrtPriceX96, poolConfiguration) = setupPoolWithVersion(version, poolConfig_, isCoinToken0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildPoolKey(address coin, address currency, bool isCoinToken0, IHooks hooks) internal pure returns (PoolKey memory poolKey) {
|
|
32
|
+
Currency currency0 = isCoinToken0 ? Currency.wrap(coin) : Currency.wrap(currency);
|
|
33
|
+
Currency currency1 = isCoinToken0 ? Currency.wrap(currency) : Currency.wrap(coin);
|
|
34
|
+
|
|
35
|
+
poolKey = PoolKey({
|
|
36
|
+
currency0: currency0,
|
|
37
|
+
currency1: currency1,
|
|
38
|
+
fee: MarketConstants.LP_FEE_V4,
|
|
39
|
+
tickSpacing: MarketConstants.TICK_SPACING,
|
|
40
|
+
hooks: hooks
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setupPoolWithVersion(
|
|
45
|
+
uint8 version,
|
|
46
|
+
bytes memory poolConfig_,
|
|
47
|
+
bool isCoinToken0
|
|
48
|
+
) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
|
|
49
|
+
if (version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
|
|
50
|
+
(sqrtPriceX96, poolConfiguration) = CoinDopplerUniV3.setupPool(isCoinToken0, poolConfig_);
|
|
51
|
+
} else if (version == CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION) {
|
|
52
|
+
(sqrtPriceX96, poolConfiguration) = CoinDopplerMultiCurve.setupPool(isCoinToken0, poolConfig_);
|
|
53
|
+
} else {
|
|
54
|
+
revert ICoin.InvalidPoolVersion();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/libs/CoinSetupV3.sol
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
import {PoolConfiguration} from "../interfaces/ICoin.sol";
|
|
5
|
-
import {CoinLegacy} from "./CoinLegacy.sol";
|
|
6
5
|
import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
|
|
7
6
|
import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
|
|
8
7
|
import {LpPosition} from "../types/LpPosition.sol";
|
|
9
8
|
import {IUniswapV3Factory} from "../interfaces/IUniswapV3Factory.sol";
|
|
10
9
|
import {MarketConstants} from "./MarketConstants.sol";
|
|
11
10
|
import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
|
|
12
|
-
|
|
11
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
12
|
+
import {CoinCommon} from "./CoinCommon.sol";
|
|
13
13
|
struct UniV3Config {
|
|
14
14
|
address weth;
|
|
15
15
|
address v3Factory;
|
|
@@ -24,75 +24,12 @@ struct CoinV3Config {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
library CoinSetupV3 {
|
|
27
|
-
error InvalidPoolVersion();
|
|
28
|
-
|
|
29
|
-
function setupPool(
|
|
30
|
-
bytes memory poolConfig_,
|
|
31
|
-
UniV3Config memory uniswapV3Config,
|
|
32
|
-
address coin
|
|
33
|
-
) internal returns (address currency, address poolAddress, PoolConfiguration memory poolConfiguration) {
|
|
34
|
-
// Extract version and currency from pool config
|
|
35
|
-
(uint8 version, address currency_) = abi.decode(poolConfig_, (uint8, address));
|
|
36
|
-
|
|
37
|
-
// Store the currency, defaulting to WETH if address(0)
|
|
38
|
-
currency = currency_ == address(0) ? uniswapV3Config.weth : currency_;
|
|
39
|
-
|
|
40
|
-
// Sort token addresses for Uniswap V3 pool creation
|
|
41
|
-
bool isCoinToken0 = _sortTokens(coin, currency);
|
|
42
|
-
address token0 = isCoinToken0 ? coin : currency;
|
|
43
|
-
address token1 = isCoinToken0 ? currency : coin;
|
|
44
|
-
|
|
45
|
-
// Configure the pool with appropriate version
|
|
46
|
-
uint160 sqrtPriceX96;
|
|
47
|
-
(sqrtPriceX96, poolConfiguration) = setupPoolWithVersion(version, poolConfig_, isCoinToken0, uniswapV3Config.weth);
|
|
48
|
-
|
|
49
|
-
// Create the pool
|
|
50
|
-
poolAddress = _createPool(token0, token1, sqrtPriceX96, uniswapV3Config.v3Factory);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
27
|
/// @dev Deploys the Uniswap V3 pool and mints initial liquidity based on the pool configuration
|
|
54
|
-
function deployLiquidity(
|
|
28
|
+
function deployLiquidity(LpPosition[] memory positions, address poolAddress) internal {
|
|
55
29
|
// Calculate and mint positions
|
|
56
|
-
LpPosition[] memory positions = calculatePositions(coin, currency, poolConfiguration);
|
|
57
30
|
_mintPositions(positions, poolAddress);
|
|
58
31
|
}
|
|
59
32
|
|
|
60
|
-
// Helper function to sort tokens and determine if coin is token0
|
|
61
|
-
function _sortTokens(address coin, address currency) private pure returns (bool isCoinToken0) {
|
|
62
|
-
return coin < currency;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function setupPoolWithVersion(
|
|
66
|
-
uint8 version,
|
|
67
|
-
bytes memory poolConfig_,
|
|
68
|
-
bool isCoinToken0,
|
|
69
|
-
address weth
|
|
70
|
-
) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
|
|
71
|
-
if (version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
|
|
72
|
-
(sqrtPriceX96, poolConfiguration) = CoinLegacy.setupPool(isCoinToken0, poolConfig_, weth);
|
|
73
|
-
} else if (version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
|
|
74
|
-
(sqrtPriceX96, poolConfiguration) = CoinDopplerUniV3.setupPool(isCoinToken0, poolConfig_);
|
|
75
|
-
} else {
|
|
76
|
-
revert InvalidPoolVersion();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function calculatePositions(
|
|
81
|
-
address coin,
|
|
82
|
-
address currency,
|
|
83
|
-
PoolConfiguration memory poolConfiguration
|
|
84
|
-
) internal pure returns (LpPosition[] memory positions) {
|
|
85
|
-
// Create the pool
|
|
86
|
-
bool isCoinToken0 = _sortTokens(coin, currency);
|
|
87
|
-
if (poolConfiguration.version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
|
|
88
|
-
positions = CoinLegacy.calculatePositions(isCoinToken0, poolConfiguration.tickLower, poolConfiguration.tickUpper);
|
|
89
|
-
} else if (poolConfiguration.version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
|
|
90
|
-
positions = CoinDopplerUniV3.calculatePositions(isCoinToken0, poolConfiguration);
|
|
91
|
-
} else {
|
|
92
|
-
revert InvalidPoolVersion();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
33
|
/// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
|
|
97
34
|
function _mintPositions(LpPosition[] memory lbpPositions, address poolAddress) internal {
|
|
98
35
|
for (uint256 i; i < lbpPositions.length; i++) {
|
|
@@ -101,7 +38,9 @@ library CoinSetupV3 {
|
|
|
101
38
|
}
|
|
102
39
|
|
|
103
40
|
/// @dev Creates the Uniswap V3 pool for the coin/currency pair
|
|
104
|
-
function
|
|
41
|
+
function createV3Pool(address coin, address currency, bool isCoinToken0, uint160 sqrtPriceX96, address v3Factory) internal returns (address pool) {
|
|
42
|
+
address token0 = isCoinToken0 ? coin : currency;
|
|
43
|
+
address token1 = isCoinToken0 ? currency : coin;
|
|
105
44
|
pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
|
|
106
45
|
|
|
107
46
|
// This pool should be new, if it has already been initialized
|