@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,752 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import "./utils/BaseTest.sol";
|
|
5
|
+
import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
|
|
6
|
+
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
|
|
7
|
+
import {IV4Quoter} from "@uniswap/v4-periphery/src/interfaces/IV4Quoter.sol";
|
|
8
|
+
import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
|
|
9
|
+
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
|
|
10
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
11
|
+
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
|
|
12
|
+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
13
|
+
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
|
|
14
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
15
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
16
|
+
import {LpPosition} from "../src/types/LpPosition.sol";
|
|
17
|
+
import {CoinCommon} from "../src/libs/CoinCommon.sol";
|
|
18
|
+
import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
|
|
19
|
+
import {IMsgSender} from "../src/interfaces/IMsgSender.sol";
|
|
20
|
+
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
|
|
21
|
+
import {toBalanceDelta, BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
22
|
+
import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
|
|
23
|
+
import {MockERC20} from "./mocks/MockERC20.sol";
|
|
24
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
25
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
26
|
+
import {FeeEstimatorHook} from "./utils/FeeEstimatorHook.sol";
|
|
27
|
+
import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
|
|
28
|
+
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
|
|
29
|
+
import {PoolStateReader} from "../src/libs/PoolStateReader.sol";
|
|
30
|
+
import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol";
|
|
31
|
+
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
|
|
32
|
+
import {ICoinV4, IHasSwapPath, PathKey} from "../src/interfaces/ICoinV4.sol";
|
|
33
|
+
import {IDeployedCoinVersionLookup} from "../src/interfaces/IDeployedCoinVersionLookup.sol";
|
|
34
|
+
|
|
35
|
+
contract CoinUniV4Test is BaseTest {
|
|
36
|
+
CoinV4 internal coinV4;
|
|
37
|
+
|
|
38
|
+
IPoolManager internal poolManager;
|
|
39
|
+
IPermit2 internal permit2;
|
|
40
|
+
IUniversalRouter internal router;
|
|
41
|
+
IV4Quoter internal quoter;
|
|
42
|
+
MockERC20 internal mockERC20A;
|
|
43
|
+
MockERC20 internal mockERC20B;
|
|
44
|
+
|
|
45
|
+
function setUp() public override {
|
|
46
|
+
super.setUpWithBlockNumber(30267794);
|
|
47
|
+
|
|
48
|
+
poolManager = IPoolManager(V4_POOL_MANAGER);
|
|
49
|
+
permit2 = IPermit2(V4_PERMIT2);
|
|
50
|
+
router = IUniversalRouter(UNIVERSAL_ROUTER);
|
|
51
|
+
quoter = IV4Quoter(V4_QUOTER);
|
|
52
|
+
mockERC20A = new MockERC20("MockERC20A", "MCKA");
|
|
53
|
+
mockERC20B = new MockERC20("MockERC20B", "MCKB");
|
|
54
|
+
|
|
55
|
+
// make sure the pool manager has some of the backing liquidity
|
|
56
|
+
// so we can take the fees in the first swap
|
|
57
|
+
mockERC20A.mint(address(poolManager), 1000000 ether);
|
|
58
|
+
mockERC20B.mint(address(poolManager), 1000000 ether);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function _defaultPoolConfig(address currency) internal pure returns (bytes memory) {
|
|
62
|
+
return CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(currency);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function _deployV4Coin(address currency) internal returns (ICoinV4) {
|
|
66
|
+
bytes32 salt = keccak256(abi.encode(bytes("randomSalt")));
|
|
67
|
+
return _deployV4Coin(currency, address(0), salt);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function _deployV4Coin(address currency, address createReferral, bytes32 salt) internal returns (ICoinV4) {
|
|
71
|
+
address[] memory owners = new address[](1);
|
|
72
|
+
owners[0] = users.creator;
|
|
73
|
+
|
|
74
|
+
bytes memory poolConfig = _defaultPoolConfig(currency);
|
|
75
|
+
|
|
76
|
+
vm.prank(users.creator);
|
|
77
|
+
(address coinAddress, ) = factory.deploy(
|
|
78
|
+
users.creator,
|
|
79
|
+
owners,
|
|
80
|
+
"https://test.com",
|
|
81
|
+
"Testcoin",
|
|
82
|
+
"TEST",
|
|
83
|
+
poolConfig,
|
|
84
|
+
createReferral,
|
|
85
|
+
address(0),
|
|
86
|
+
bytes(""),
|
|
87
|
+
salt
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
coinV4 = CoinV4(payable(coinAddress));
|
|
91
|
+
return coinV4;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// @dev Estimates the fees from a swap, by deploying a test hook that doesn't distribute the fees
|
|
95
|
+
/// and then reverting the state after the swap
|
|
96
|
+
function _estimateLpFees(bytes memory commands, bytes[] memory inputs) internal returns (FeeEstimatorHook.FeeEstimatorState memory feeState) {
|
|
97
|
+
uint256 snapshot = vm.snapshot();
|
|
98
|
+
deployCodeTo("FeeEstimatorHook.sol", abi.encode(address(poolManager), address(factory)), address(coinV4.hooks()));
|
|
99
|
+
|
|
100
|
+
// Execute the swap
|
|
101
|
+
uint256 deadline = block.timestamp + 20;
|
|
102
|
+
router.execute(commands, inputs, deadline);
|
|
103
|
+
|
|
104
|
+
feeState = FeeEstimatorHook(address(coinV4.hooks())).getFeeState();
|
|
105
|
+
|
|
106
|
+
vm.revertToState(snapshot);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function _swapSomeCurrencyForCoin(ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
|
|
110
|
+
uint128 minAmountOut = uint128(0);
|
|
111
|
+
|
|
112
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
113
|
+
currency,
|
|
114
|
+
amountIn,
|
|
115
|
+
address(_coin),
|
|
116
|
+
minAmountOut,
|
|
117
|
+
_coin.getPoolKey(),
|
|
118
|
+
bytes("")
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
vm.startPrank(trader);
|
|
122
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
|
|
123
|
+
|
|
124
|
+
// Execute the swap
|
|
125
|
+
uint256 deadline = block.timestamp + 20;
|
|
126
|
+
router.execute(commands, inputs, deadline);
|
|
127
|
+
|
|
128
|
+
vm.stopPrank();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _swapSomeCoinForCurrency(ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
|
|
132
|
+
uint128 minAmountOut = uint128(0);
|
|
133
|
+
|
|
134
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
135
|
+
address(_coin),
|
|
136
|
+
amountIn,
|
|
137
|
+
currency,
|
|
138
|
+
minAmountOut,
|
|
139
|
+
_coin.getPoolKey(),
|
|
140
|
+
bytes("")
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
vm.startPrank(trader);
|
|
144
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(_coin), amountIn, uint48(block.timestamp + 1 days));
|
|
145
|
+
|
|
146
|
+
// Execute the swap
|
|
147
|
+
uint256 deadline = block.timestamp + 20;
|
|
148
|
+
router.execute(commands, inputs, deadline);
|
|
149
|
+
|
|
150
|
+
vm.stopPrank();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// and then reverting the state after the swap
|
|
154
|
+
function _estimateSwap(
|
|
155
|
+
bytes memory commands,
|
|
156
|
+
bytes[] memory inputs
|
|
157
|
+
) internal returns (BalanceDelta delta, SwapParams memory swapParams, uint160 sqrtPriceX96) {
|
|
158
|
+
uint256 snapshot = vm.snapshot();
|
|
159
|
+
deployCodeTo("FeeEstimatorHook.sol", abi.encode(address(poolManager), address(factory)), address(coinV4.hooks()));
|
|
160
|
+
|
|
161
|
+
// Execute the swap
|
|
162
|
+
uint256 deadline = block.timestamp + 20;
|
|
163
|
+
router.execute(commands, inputs, deadline);
|
|
164
|
+
|
|
165
|
+
delta = FeeEstimatorHook(address(coinV4.hooks())).getFeeState().lastDelta;
|
|
166
|
+
swapParams = FeeEstimatorHook(address(coinV4.hooks())).getFeeState().lastSwapParams;
|
|
167
|
+
|
|
168
|
+
sqrtPriceX96 = PoolStateReader.getSqrtPriceX96(coinV4.getPoolKey(), poolManager);
|
|
169
|
+
|
|
170
|
+
vm.revertToState(snapshot);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function test_setupZeroAddressForPoolManager() public {
|
|
174
|
+
vm.expectRevert(ICoin.AddressZero.selector);
|
|
175
|
+
new CoinV4({
|
|
176
|
+
protocolRewardRecipient_: address(0x1234),
|
|
177
|
+
protocolRewards_: address(0x1234),
|
|
178
|
+
poolManager_: IPoolManager(address(0)),
|
|
179
|
+
airlock_: address(0),
|
|
180
|
+
hooks_: IHooks(address(0))
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function test_setupZeroAddressForHooks() public {
|
|
185
|
+
vm.expectRevert(ICoin.AddressZero.selector);
|
|
186
|
+
new CoinV4({
|
|
187
|
+
protocolRewardRecipient_: address(0x1234),
|
|
188
|
+
protocolRewards_: address(0x1234),
|
|
189
|
+
poolManager_: IPoolManager(address(0x1234)),
|
|
190
|
+
airlock_: address(0x1234),
|
|
191
|
+
hooks_: IHooks(address(0))
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function test_estimateLpFees() public {
|
|
196
|
+
address currency = address(mockERC20A);
|
|
197
|
+
_deployV4Coin(currency);
|
|
198
|
+
|
|
199
|
+
uint128 amountIn = uint128(0.00001 ether);
|
|
200
|
+
uint128 minAmountOut = uint128(0);
|
|
201
|
+
|
|
202
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
203
|
+
currency,
|
|
204
|
+
amountIn,
|
|
205
|
+
address(coinV4),
|
|
206
|
+
minAmountOut,
|
|
207
|
+
coinV4.getPoolKey(),
|
|
208
|
+
bytes("")
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
address trader = makeAddr("trader");
|
|
212
|
+
|
|
213
|
+
// mint some mockERC20 to the trader, so they can use it to buy the coin
|
|
214
|
+
mockERC20A.mint(trader, 1 ether);
|
|
215
|
+
|
|
216
|
+
// have trader approve to permit2
|
|
217
|
+
vm.startPrank(trader);
|
|
218
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, type(uint128).max, uint48(block.timestamp + 1 days));
|
|
219
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(coinV4), type(uint128).max, uint48(block.timestamp + 1 days));
|
|
220
|
+
|
|
221
|
+
// do a fake swap, so we can estimate LP fees
|
|
222
|
+
FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
|
|
223
|
+
|
|
224
|
+
bool isCoinToken0 = Currency.unwrap(coinV4.getPoolKey().currency0) == address(coinV4);
|
|
225
|
+
uint128 feeCoin = isCoinToken0 ? feeState.fees0 : feeState.fees1;
|
|
226
|
+
uint128 feeCurrency = isCoinToken0 ? feeState.fees1 : feeState.fees0;
|
|
227
|
+
|
|
228
|
+
assertEq(feeCoin, 0, "fee coin should be 0");
|
|
229
|
+
assertGt(feeCurrency, 0, "fee currency should be greater than to 0");
|
|
230
|
+
assertGt(feeState.afterSwapCurrencyAmount, 0, "after swap fee currency should be greater than 0");
|
|
231
|
+
|
|
232
|
+
// execute the swap
|
|
233
|
+
router.execute(commands, inputs, block.timestamp + 20);
|
|
234
|
+
|
|
235
|
+
// now estimate fees in swap back to currency
|
|
236
|
+
(commands, inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
237
|
+
address(coinV4),
|
|
238
|
+
feeCurrency,
|
|
239
|
+
currency,
|
|
240
|
+
minAmountOut,
|
|
241
|
+
coinV4.getPoolKey(),
|
|
242
|
+
bytes("")
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
FeeEstimatorHook.FeeEstimatorState memory newFeeState = _estimateLpFees(commands, inputs);
|
|
246
|
+
|
|
247
|
+
uint128 newFeeCoin = isCoinToken0 ? newFeeState.fees0 : newFeeState.fees1;
|
|
248
|
+
uint128 newFeeCurrency = isCoinToken0 ? newFeeState.fees1 : newFeeState.fees0;
|
|
249
|
+
|
|
250
|
+
assertGt(newFeeCoin, 0, "fee coin on second swap should be greater than 0");
|
|
251
|
+
assertEq(newFeeCurrency, 0, "fee currency on second swap should be greater than 0");
|
|
252
|
+
assertGt(newFeeState.afterSwapCurrencyAmount, 0, "after swap fee currency on second swap should be greater than 0");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
uint256 public constant CREATOR_REWARD_BPS = 5000;
|
|
256
|
+
uint256 public constant CREATE_REFERRAL_REWARD_BPS = 1500;
|
|
257
|
+
uint256 public constant TRADE_REFERRAL_REWARD_BPS = 1500;
|
|
258
|
+
uint256 public constant DOPPLER_REWARD_BPS = 500;
|
|
259
|
+
|
|
260
|
+
function computeExpectedRewards(
|
|
261
|
+
uint256 fee,
|
|
262
|
+
bool hasCreateReferral,
|
|
263
|
+
bool hasTradeReferral
|
|
264
|
+
)
|
|
265
|
+
internal
|
|
266
|
+
pure
|
|
267
|
+
returns (
|
|
268
|
+
uint256 backingRewardsCurrency,
|
|
269
|
+
uint256 dopplerRewardsCurrency,
|
|
270
|
+
uint256 createReferralRewardsCurrency,
|
|
271
|
+
uint256 tradeReferralRewardsCurrency,
|
|
272
|
+
uint256 protocolRewardsCurrency
|
|
273
|
+
)
|
|
274
|
+
{
|
|
275
|
+
backingRewardsCurrency = CoinRewardsV4.calculateReward(fee, CREATOR_REWARD_BPS);
|
|
276
|
+
dopplerRewardsCurrency = CoinRewardsV4.calculateReward(fee, DOPPLER_REWARD_BPS);
|
|
277
|
+
createReferralRewardsCurrency = hasCreateReferral ? CoinRewardsV4.calculateReward(fee, CREATE_REFERRAL_REWARD_BPS) : 0;
|
|
278
|
+
tradeReferralRewardsCurrency = hasTradeReferral ? CoinRewardsV4.calculateReward(fee, TRADE_REFERRAL_REWARD_BPS) : 0;
|
|
279
|
+
protocolRewardsCurrency = fee - backingRewardsCurrency - dopplerRewardsCurrency - createReferralRewardsCurrency - tradeReferralRewardsCurrency;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function test_distributesMarketRewards(uint64 amountIn, bool hasCreateReferral, bool hasTradeReferral) public {
|
|
283
|
+
vm.assume(amountIn > 0.00001 ether);
|
|
284
|
+
address currency = address(mockERC20A);
|
|
285
|
+
address createReferral = hasCreateReferral ? makeAddr("createReferral") : address(0);
|
|
286
|
+
address tradeReferral = hasTradeReferral ? makeAddr("tradeReferral") : address(0);
|
|
287
|
+
bytes32 salt = keccak256(abi.encodePacked(amountIn, hasCreateReferral, hasTradeReferral));
|
|
288
|
+
_deployV4Coin(currency, createReferral, salt);
|
|
289
|
+
|
|
290
|
+
uint256 balanceBeforePayoutRecipient = coinV4.balanceOf(coinV4.payoutRecipient());
|
|
291
|
+
|
|
292
|
+
uint128 minAmountOut = uint128(0);
|
|
293
|
+
|
|
294
|
+
bytes memory hookData = hasTradeReferral ? abi.encode(tradeReferral) : bytes("");
|
|
295
|
+
|
|
296
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
297
|
+
currency,
|
|
298
|
+
amountIn,
|
|
299
|
+
address(coinV4),
|
|
300
|
+
minAmountOut,
|
|
301
|
+
coinV4.getPoolKey(),
|
|
302
|
+
hookData
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
address trader = makeAddr("trader");
|
|
306
|
+
|
|
307
|
+
// mint some mockERC20 to the trader, so they can use it to buy the coin
|
|
308
|
+
mockERC20A.mint(trader, amountIn);
|
|
309
|
+
|
|
310
|
+
// have trader approve to permit2
|
|
311
|
+
vm.startPrank(trader);
|
|
312
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
|
|
313
|
+
|
|
314
|
+
// do a fake swap, so we can estimate LP fees
|
|
315
|
+
FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
|
|
316
|
+
|
|
317
|
+
(
|
|
318
|
+
uint256 backingRewardsCurrency,
|
|
319
|
+
uint256 dopplerRewardsCurrency,
|
|
320
|
+
uint256 createReferralRewardsCurrency,
|
|
321
|
+
uint256 tradeReferralRewardsCurrency,
|
|
322
|
+
uint256 protocolRewardsCurrency
|
|
323
|
+
) = computeExpectedRewards(feeState.afterSwapCurrencyAmount, hasCreateReferral, hasTradeReferral);
|
|
324
|
+
|
|
325
|
+
// Execute the swap
|
|
326
|
+
router.execute(commands, inputs, block.timestamp + 20);
|
|
327
|
+
|
|
328
|
+
// now do a swap back to the currency
|
|
329
|
+
(commands, inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(address(coinV4), amountIn, currency, minAmountOut, coinV4.getPoolKey(), hookData);
|
|
330
|
+
|
|
331
|
+
// approve the coinV4 to spend the coin
|
|
332
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(coinV4), amountIn, uint48(block.timestamp + 1 days));
|
|
333
|
+
|
|
334
|
+
// estimate the new LP fees
|
|
335
|
+
FeeEstimatorHook.FeeEstimatorState memory newFeeState = _estimateLpFees(commands, inputs);
|
|
336
|
+
|
|
337
|
+
(
|
|
338
|
+
uint256 backingRewardsCurrency2,
|
|
339
|
+
uint256 dopplerRewardsCurrency2,
|
|
340
|
+
uint256 createReferralRewardsCurrency2,
|
|
341
|
+
uint256 tradeReferralRewardsCurrency2,
|
|
342
|
+
uint256 protocolRewardsCurrency2
|
|
343
|
+
) = computeExpectedRewards(newFeeState.afterSwapCurrencyAmount, hasCreateReferral, hasTradeReferral);
|
|
344
|
+
|
|
345
|
+
// now do a swap, rewards balance changes of both the coin and the currency should reflect the new fees
|
|
346
|
+
router.execute(commands, inputs, block.timestamp + 20);
|
|
347
|
+
|
|
348
|
+
assertEq(coinV4.balanceOf(coinV4.payoutRecipient()) - balanceBeforePayoutRecipient, 0, "backing reward coin");
|
|
349
|
+
assertEq(coinV4.balanceOf(coinV4.dopplerFeeRecipient()), 0, "doppler reward coin");
|
|
350
|
+
if (hasCreateReferral) {
|
|
351
|
+
assertEq(coinV4.balanceOf(createReferral), 0, "create referral reward coin");
|
|
352
|
+
}
|
|
353
|
+
if (hasTradeReferral) {
|
|
354
|
+
assertEq(coinV4.balanceOf(tradeReferral), 0, "trade referral reward coin");
|
|
355
|
+
}
|
|
356
|
+
assertEq(coinV4.balanceOf(coinV4.protocolRewardRecipient()), 0, "protocol reward coin");
|
|
357
|
+
|
|
358
|
+
assertEq(mockERC20A.balanceOf(coinV4.payoutRecipient()), backingRewardsCurrency + backingRewardsCurrency2, "backing reward currency");
|
|
359
|
+
assertEq(mockERC20A.balanceOf(coinV4.dopplerFeeRecipient()), dopplerRewardsCurrency + dopplerRewardsCurrency2, "doppler reward currency");
|
|
360
|
+
if (hasCreateReferral) {
|
|
361
|
+
assertEq(mockERC20A.balanceOf(createReferral), createReferralRewardsCurrency + createReferralRewardsCurrency2, "create referral reward currency");
|
|
362
|
+
}
|
|
363
|
+
if (hasTradeReferral) {
|
|
364
|
+
assertEq(mockERC20A.balanceOf(tradeReferral), tradeReferralRewardsCurrency + tradeReferralRewardsCurrency2, "trade referral reward currency");
|
|
365
|
+
}
|
|
366
|
+
assertEq(mockERC20A.balanceOf(coinV4.protocolRewardRecipient()), protocolRewardsCurrency + protocolRewardsCurrency2, "protocol reward currency");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function test_swap_emitsCoinMarketRewardsV4(uint64 amountIn) public {
|
|
370
|
+
vm.assume(amountIn > 0.00001 ether);
|
|
371
|
+
address currency = address(mockERC20A);
|
|
372
|
+
address createReferral = makeAddr("createReferral");
|
|
373
|
+
address tradeReferral = makeAddr("tradeReferral");
|
|
374
|
+
bytes32 salt = keccak256(abi.encode(bytes("randomSalt")));
|
|
375
|
+
_deployV4Coin(currency, createReferral, salt);
|
|
376
|
+
|
|
377
|
+
uint128 minAmountOut = uint128(0);
|
|
378
|
+
|
|
379
|
+
bytes memory hookData = abi.encode(tradeReferral);
|
|
380
|
+
|
|
381
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
382
|
+
currency,
|
|
383
|
+
amountIn,
|
|
384
|
+
address(coinV4),
|
|
385
|
+
minAmountOut,
|
|
386
|
+
coinV4.getPoolKey(),
|
|
387
|
+
hookData
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
address trader = makeAddr("trader");
|
|
391
|
+
|
|
392
|
+
// mint some mockERC20 to the trader, so they can use it to buy the coin
|
|
393
|
+
mockERC20A.mint(trader, amountIn);
|
|
394
|
+
|
|
395
|
+
// have trader approve to permit2
|
|
396
|
+
vm.startPrank(trader);
|
|
397
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
|
|
398
|
+
|
|
399
|
+
// do a fake swap, so we can estimate LP fees
|
|
400
|
+
FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
|
|
401
|
+
|
|
402
|
+
(
|
|
403
|
+
uint256 backingRewardsCurrency,
|
|
404
|
+
uint256 dopplerRewardsCurrency,
|
|
405
|
+
uint256 createReferralRewardsCurrency,
|
|
406
|
+
uint256 tradeReferralRewardsCurrency,
|
|
407
|
+
uint256 protocolRewardsCurrency
|
|
408
|
+
) = computeExpectedRewards(feeState.afterSwapCurrencyAmount, true, true);
|
|
409
|
+
|
|
410
|
+
vm.expectEmit(true, true, true, true);
|
|
411
|
+
emit IZoraV4CoinHook.CoinMarketRewardsV4(
|
|
412
|
+
address(coinV4),
|
|
413
|
+
address(mockERC20A),
|
|
414
|
+
coinV4.payoutRecipient(),
|
|
415
|
+
createReferral,
|
|
416
|
+
tradeReferral,
|
|
417
|
+
coinV4.protocolRewardRecipient(),
|
|
418
|
+
coinV4.dopplerFeeRecipient(),
|
|
419
|
+
IZoraV4CoinHook.MarketRewardsV4({
|
|
420
|
+
creatorPayoutAmountCurrency: backingRewardsCurrency,
|
|
421
|
+
creatorPayoutAmountCoin: 0,
|
|
422
|
+
platformReferrerAmountCurrency: createReferralRewardsCurrency,
|
|
423
|
+
platformReferrerAmountCoin: 0,
|
|
424
|
+
tradeReferrerAmountCurrency: tradeReferralRewardsCurrency,
|
|
425
|
+
tradeReferrerAmountCoin: 0,
|
|
426
|
+
protocolAmountCurrency: protocolRewardsCurrency,
|
|
427
|
+
protocolAmountCoin: 0,
|
|
428
|
+
dopplerAmountCurrency: dopplerRewardsCurrency,
|
|
429
|
+
dopplerAmountCoin: 0
|
|
430
|
+
})
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// Execute the swap
|
|
434
|
+
router.execute(commands, inputs, block.timestamp + 20);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function test_canGetQuoteForSwappingCurrencyForCoin() public {
|
|
438
|
+
address currency = address(mockERC20A);
|
|
439
|
+
_deployV4Coin(currency);
|
|
440
|
+
|
|
441
|
+
bool isCoinToken0 = CoinCommon.sortTokens(address(coinV4), currency);
|
|
442
|
+
|
|
443
|
+
// we want to swap currency for coin
|
|
444
|
+
bool zeroForOne = !isCoinToken0;
|
|
445
|
+
|
|
446
|
+
IV4Quoter.QuoteExactSingleParams memory quoteParams = IV4Quoter.QuoteExactSingleParams({
|
|
447
|
+
poolKey: coinV4.getPoolKey(),
|
|
448
|
+
zeroForOne: zeroForOne,
|
|
449
|
+
exactAmount: 1 ether,
|
|
450
|
+
hookData: bytes("")
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
(uint256 amountOut, ) = quoter.quoteExactInputSingle(quoteParams);
|
|
454
|
+
|
|
455
|
+
assertGt(amountOut, 0);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function test_canSwapCurrencyForCoin() public {
|
|
459
|
+
address currency = address(mockERC20A);
|
|
460
|
+
_deployV4Coin(currency);
|
|
461
|
+
|
|
462
|
+
uint128 amountIn = uint128(0.00001 ether);
|
|
463
|
+
|
|
464
|
+
uint128 minAmountOut = 0;
|
|
465
|
+
|
|
466
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
467
|
+
currency,
|
|
468
|
+
amountIn,
|
|
469
|
+
address(coinV4),
|
|
470
|
+
minAmountOut,
|
|
471
|
+
coinV4.getPoolKey(),
|
|
472
|
+
bytes("")
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
address trader = makeAddr("trader");
|
|
476
|
+
|
|
477
|
+
uint128 initialTraderBalance = uint128(1 ether);
|
|
478
|
+
|
|
479
|
+
// mint some mockERC20 to the trader, so they can use it to buy the coin
|
|
480
|
+
mockERC20A.mint(trader, initialTraderBalance);
|
|
481
|
+
|
|
482
|
+
// have trader approve to permit2
|
|
483
|
+
vm.startPrank(trader);
|
|
484
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
|
|
485
|
+
|
|
486
|
+
// Execute the swap
|
|
487
|
+
uint256 deadline = block.timestamp + 20;
|
|
488
|
+
router.execute(commands, inputs, deadline);
|
|
489
|
+
|
|
490
|
+
assertEq(mockERC20A.balanceOf(trader), initialTraderBalance - amountIn);
|
|
491
|
+
assertGt(coinV4.balanceOf(trader), minAmountOut);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function test_canSwapCoinForCurrency() public {
|
|
495
|
+
address currency = address(mockERC20A);
|
|
496
|
+
_deployV4Coin(currency);
|
|
497
|
+
|
|
498
|
+
uint128 currencyIn = uint128(0.00001 ether);
|
|
499
|
+
|
|
500
|
+
address trader = makeAddr("trader");
|
|
501
|
+
|
|
502
|
+
mockERC20A.mint(trader, currencyIn);
|
|
503
|
+
|
|
504
|
+
// swap some currency for coin so that the pool has some balance to work with
|
|
505
|
+
_swapSomeCurrencyForCoin(coinV4, currency, currencyIn, trader);
|
|
506
|
+
|
|
507
|
+
uint128 coinIn = uint128(coinV4.balanceOf(trader));
|
|
508
|
+
// now swap coin for currency
|
|
509
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
510
|
+
address(coinV4),
|
|
511
|
+
coinIn,
|
|
512
|
+
currency,
|
|
513
|
+
0,
|
|
514
|
+
coinV4.getPoolKey(),
|
|
515
|
+
bytes("")
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
vm.startPrank(trader);
|
|
519
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(coinV4), coinIn, uint48(block.timestamp + 1 days));
|
|
520
|
+
|
|
521
|
+
router.execute(commands, inputs, block.timestamp + 20);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function testSwappingEmitsSwapEventFromSenderNoRevert() public {
|
|
525
|
+
_callSwappingEmitsSwapEvent(false, false);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function testSwappingEmitsSwapEventFromSenderReverts() public {
|
|
529
|
+
_callSwappingEmitsSwapEvent(false, true);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function testSwappingEmitsSwapEventFromTrustedMessageSenderNoRevert() public {
|
|
533
|
+
_callSwappingEmitsSwapEvent(true, false);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function testSwappingEmitsSwapEventFromTrustedMessageSenderReverts() public {
|
|
537
|
+
_callSwappingEmitsSwapEvent(true, true);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function test_afterInitializeRevertsWhenSenderIsNotACoin() public {
|
|
541
|
+
// First deploy a coin so we have a valid hook to test against
|
|
542
|
+
address currency = address(mockERC20A);
|
|
543
|
+
_deployV4Coin(currency);
|
|
544
|
+
|
|
545
|
+
// Deploy a mock contract that is not a coin
|
|
546
|
+
MockERC20 notACoin = new MockERC20("NotACoin", "NAC");
|
|
547
|
+
|
|
548
|
+
bool isCoinToken0 = CoinCommon.sortTokens(address(coinV4), address(notACoin));
|
|
549
|
+
|
|
550
|
+
// Create a valid pool key
|
|
551
|
+
PoolKey memory key = PoolKey({
|
|
552
|
+
currency0: isCoinToken0 ? Currency.wrap(address(coinV4)) : Currency.wrap(address(notACoin)),
|
|
553
|
+
currency1: isCoinToken0 ? Currency.wrap(address(notACoin)) : Currency.wrap(address(coinV4)),
|
|
554
|
+
fee: 3000,
|
|
555
|
+
tickSpacing: 60,
|
|
556
|
+
hooks: IHooks(address(coinV4.hooks()))
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// We need to prank the call to come from the non-coin contract
|
|
560
|
+
vm.startPrank(address(notACoin));
|
|
561
|
+
|
|
562
|
+
// The hook should revert with NotACoin error when initializing
|
|
563
|
+
vm.expectRevert(
|
|
564
|
+
abi.encodeWithSelector(
|
|
565
|
+
CustomRevert.WrappedError.selector,
|
|
566
|
+
address(coinV4.hooks()),
|
|
567
|
+
IHooks.afterInitialize.selector,
|
|
568
|
+
abi.encodeWithSelector(IZoraV4CoinHook.NotACoin.selector, address(notACoin)),
|
|
569
|
+
abi.encodeWithSelector(Hooks.HookCallFailed.selector)
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
// Call the pool manager to initialize, the hook should revert because the calling coin is not a coin
|
|
574
|
+
poolManager.initialize(key, uint160(1049428825694136384760392514097686388));
|
|
575
|
+
|
|
576
|
+
vm.stopPrank();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function _callSwappingEmitsSwapEvent(bool swapIsFromTrustedMessageSender, bool trustedSenderReverts) internal {
|
|
580
|
+
uint64 amountIn = uint64(0.1 ether);
|
|
581
|
+
address currency = address(mockERC20A);
|
|
582
|
+
_deployV4Coin(currency);
|
|
583
|
+
|
|
584
|
+
uint128 minAmountOut = uint128(0);
|
|
585
|
+
|
|
586
|
+
PoolKey memory key = coinV4.getPoolKey();
|
|
587
|
+
|
|
588
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
589
|
+
currency,
|
|
590
|
+
amountIn,
|
|
591
|
+
address(coinV4),
|
|
592
|
+
minAmountOut,
|
|
593
|
+
key,
|
|
594
|
+
bytes("")
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
address trader = makeAddr("trader");
|
|
598
|
+
|
|
599
|
+
// mint some mockERC20 to the trader, so they can use it to buy the coin
|
|
600
|
+
mockERC20A.mint(trader, amountIn);
|
|
601
|
+
|
|
602
|
+
// have trader approve to permit2
|
|
603
|
+
vm.startPrank(trader);
|
|
604
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
|
|
605
|
+
|
|
606
|
+
// Execute the swap
|
|
607
|
+
uint256 deadline = block.timestamp + 20;
|
|
608
|
+
|
|
609
|
+
address sender = UNIVERSAL_ROUTER;
|
|
610
|
+
|
|
611
|
+
(BalanceDelta delta, SwapParams memory swapParams, uint160 sqrtPriceX96) = _estimateSwap(commands, inputs);
|
|
612
|
+
|
|
613
|
+
address[] memory _trustedMessageSenders = new address[](1);
|
|
614
|
+
_trustedMessageSenders[0] = UNIVERSAL_ROUTER;
|
|
615
|
+
|
|
616
|
+
// if we want to simulate swap happening from a non-trusted message sender, we copy the router code to a new address that isn't
|
|
617
|
+
// trusted by the hook, and have that router execute the swap
|
|
618
|
+
if (!swapIsFromTrustedMessageSender) {
|
|
619
|
+
bytes memory routerCode = address(router).code;
|
|
620
|
+
address targetAddr = makeAddr("targetAddr");
|
|
621
|
+
vm.etch(targetAddr, routerCode);
|
|
622
|
+
router = IUniversalRouter(targetAddr);
|
|
623
|
+
// we need to approve the token again on the new router address.
|
|
624
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// if we want to simulate the trusted sender reverting, we mock the msgSender function on the router to revert
|
|
628
|
+
if (trustedSenderReverts) {
|
|
629
|
+
vm.mockCallRevert(address(router), abi.encodeWithSelector(IMsgSender.msgSender.selector), "any revert");
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
bool isCoinBuy = true;
|
|
633
|
+
|
|
634
|
+
vm.expectEmit(false, true, true, true);
|
|
635
|
+
emit IZoraV4CoinHook.Swapped(
|
|
636
|
+
sender,
|
|
637
|
+
trustedSenderReverts ? address(0) : trader,
|
|
638
|
+
swapIsFromTrustedMessageSender,
|
|
639
|
+
key,
|
|
640
|
+
CoinCommon.hashPoolKey(key),
|
|
641
|
+
swapParams,
|
|
642
|
+
delta.amount0(),
|
|
643
|
+
delta.amount1(),
|
|
644
|
+
isCoinBuy,
|
|
645
|
+
"",
|
|
646
|
+
sqrtPriceX96
|
|
647
|
+
);
|
|
648
|
+
router.execute(commands, inputs, deadline);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function test_getSwapPath_whenBackingCurrencyIsErc20() public {
|
|
652
|
+
address currency = address(mockERC20A);
|
|
653
|
+
_deployV4Coin(currency);
|
|
654
|
+
|
|
655
|
+
IHasSwapPath.PayoutSwapPath memory swapPath = coinV4.getPayoutSwapPath(IDeployedCoinVersionLookup(address(factory)));
|
|
656
|
+
|
|
657
|
+
assertEq(swapPath.path.length, 1);
|
|
658
|
+
_assertPathKeyEqual(
|
|
659
|
+
swapPath.path[0],
|
|
660
|
+
PathKey({
|
|
661
|
+
intermediateCurrency: Currency.wrap(address(mockERC20A)),
|
|
662
|
+
fee: coinV4.getPoolKey().fee,
|
|
663
|
+
tickSpacing: coinV4.getPoolKey().tickSpacing,
|
|
664
|
+
hooks: coinV4.getPoolKey().hooks,
|
|
665
|
+
hookData: bytes("")
|
|
666
|
+
}),
|
|
667
|
+
"path key"
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function test_getSwapPath_whenBackingCurrencyProvidesPath() public {
|
|
672
|
+
address zora = address(mockERC20A);
|
|
673
|
+
ICoinV4 backingCoin = _deployV4Coin(zora);
|
|
674
|
+
// now create a final coin paired with the backing coin
|
|
675
|
+
ICoinV4 contentCoin = _deployV4Coin(address(backingCoin));
|
|
676
|
+
|
|
677
|
+
PathKey[] memory path = contentCoin.getPayoutSwapPath(IDeployedCoinVersionLookup(address(factory))).path;
|
|
678
|
+
|
|
679
|
+
// swap path should be:
|
|
680
|
+
// 1. content coin -> backing coin
|
|
681
|
+
// 2. backing coin -> zora coin
|
|
682
|
+
assertEq(path.length, 2);
|
|
683
|
+
_assertPathKeyEqual(
|
|
684
|
+
path[0],
|
|
685
|
+
PathKey({
|
|
686
|
+
intermediateCurrency: Currency.wrap(address(backingCoin)),
|
|
687
|
+
fee: contentCoin.getPoolKey().fee,
|
|
688
|
+
tickSpacing: contentCoin.getPoolKey().tickSpacing,
|
|
689
|
+
hooks: contentCoin.getPoolKey().hooks,
|
|
690
|
+
hookData: bytes("")
|
|
691
|
+
}),
|
|
692
|
+
"content to backing coin"
|
|
693
|
+
);
|
|
694
|
+
_assertPathKeyEqual(
|
|
695
|
+
path[1],
|
|
696
|
+
PathKey({
|
|
697
|
+
intermediateCurrency: Currency.wrap(zora),
|
|
698
|
+
fee: backingCoin.getPoolKey().fee,
|
|
699
|
+
tickSpacing: backingCoin.getPoolKey().tickSpacing,
|
|
700
|
+
hooks: backingCoin.getPoolKey().hooks,
|
|
701
|
+
hookData: bytes("")
|
|
702
|
+
}),
|
|
703
|
+
"backing to zora coin"
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function test_swap_withBackingCoinToZora_paysRewardsInZoraOnly(uint128 amountIn) public {
|
|
708
|
+
// zora is a mock erc20
|
|
709
|
+
address zora = address(mockERC20A);
|
|
710
|
+
// make sure pool manager has enough zora in it so we can take the fees on the swap
|
|
711
|
+
mockERC20A.mint(address(poolManager), 10000000000000000 ether);
|
|
712
|
+
|
|
713
|
+
// backing coin is a mock coin that is paired with zora
|
|
714
|
+
ICoinV4 backingCoin = _deployV4Coin(zora);
|
|
715
|
+
// now create a final coin paired with the backing coin
|
|
716
|
+
ICoinV4 contentCoin = _deployV4Coin(address(backingCoin));
|
|
717
|
+
|
|
718
|
+
vm.assume(amountIn > 0.000000000001 ether);
|
|
719
|
+
vm.assume(amountIn < 10000000000000000 ether);
|
|
720
|
+
|
|
721
|
+
address trader = makeAddr("trader");
|
|
722
|
+
MockERC20(zora).mint(trader, amountIn);
|
|
723
|
+
|
|
724
|
+
address protocolRewardRecipient = contentCoin.protocolRewardRecipient();
|
|
725
|
+
|
|
726
|
+
// swap some zora for backing coin, so the trader has some backing coin - this should not cause a multihop swap for rewards
|
|
727
|
+
_swapSomeCurrencyForCoin(backingCoin, zora, uint128(IERC20(address(zora)).balanceOf(trader)), trader);
|
|
728
|
+
|
|
729
|
+
// get balances before
|
|
730
|
+
uint256 protocolRewardRecipientZoraBalanceBefore = IERC20(address(zora)).balanceOf(protocolRewardRecipient);
|
|
731
|
+
uint256 protocolRewardRecipientBackingCoinBalanceBefore = IERC20(address(backingCoin)).balanceOf(protocolRewardRecipient);
|
|
732
|
+
uint256 protocolRewardRecipientContentCoinBalanceBefore = IERC20(address(contentCoin)).balanceOf(protocolRewardRecipient);
|
|
733
|
+
|
|
734
|
+
// swap some backing coin for content coin, this should do final rewards transfer in correct balance
|
|
735
|
+
_swapSomeCurrencyForCoin(contentCoin, address(backingCoin), uint128(IERC20(address(backingCoin)).balanceOf(trader)), trader);
|
|
736
|
+
|
|
737
|
+
// swap some content coin for backing coin, this should do final rewards transfer in correct balance
|
|
738
|
+
_swapSomeCoinForCurrency(contentCoin, address(backingCoin), uint128(IERC20(address(contentCoin)).balanceOf(trader)), trader);
|
|
739
|
+
|
|
740
|
+
// make sure that no zora, backing, or content coin was paid out to the protocol reward recipient, but just usdc was
|
|
741
|
+
assertEq(IERC20(address(backingCoin)).balanceOf(protocolRewardRecipient), protocolRewardRecipientBackingCoinBalanceBefore, "backing coin was paid out");
|
|
742
|
+
assertEq(IERC20(address(contentCoin)).balanceOf(protocolRewardRecipient), protocolRewardRecipientContentCoinBalanceBefore, "content coin was paid out");
|
|
743
|
+
assertGt(IERC20(address(zora)).balanceOf(protocolRewardRecipient), protocolRewardRecipientZoraBalanceBefore, "zora was paid out");
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function _assertPathKeyEqual(PathKey memory a, PathKey memory b, string memory keyName) internal pure {
|
|
747
|
+
assertEq(Currency.unwrap(a.intermediateCurrency), Currency.unwrap(b.intermediateCurrency), string.concat(keyName, " intermediateCurrency"));
|
|
748
|
+
assertEq(a.fee, b.fee, string.concat(keyName, " fee"));
|
|
749
|
+
assertEq(a.tickSpacing, b.tickSpacing, string.concat(keyName, " tickSpacing"));
|
|
750
|
+
assertEq(address(a.hooks), address(b.hooks), string.concat(keyName, " hooks"));
|
|
751
|
+
}
|
|
752
|
+
}
|