@zoralabs/coins 0.9.0 → 1.0.1
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 +179 -114
- package/CHANGELOG.md +46 -0
- package/abis/BaseCoin.json +26 -118
- package/abis/BaseTest.json +47 -0
- package/abis/BuySupplyWithSwapRouterHook.json +40 -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 +1091 -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 +1515 -0
- package/abis/HooksDeployment.json +23 -0
- package/abis/HooksTest.json +60 -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 +210 -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 +81 -0
- package/abis/Vm.json +1482 -111
- package/abis/VmSafe.json +856 -32
- package/abis/ZoraFactoryImpl.json +339 -1
- package/abis/ZoraV4CoinHook.json +442 -5
- package/addresses/8453.json +7 -4
- package/addresses/84532.json +8 -5
- package/addresses/dev/8453.json +10 -0
- package/dist/index.cjs +1932 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1928 -167
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +2606 -160
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/foundry.toml +1 -0
- package/package/wagmiGenerated.ts +1941 -164
- package/package.json +8 -3
- package/remappings.txt +6 -1
- package/script/Deploy.s.sol +1 -1
- package/script/DeployDevFactory.s.sol +21 -0
- package/script/DeployHooks.s.sol +1 -1
- package/script/PrintUpgradeCommand.s.sol +13 -0
- package/script/Simulate.s.sol +1 -10
- package/script/TestBackingCoinSwap.s.sol +147 -0
- package/script/TestV4Swap.s.sol +136 -0
- package/script/UpgradeCoinImpl.sol +2 -2
- package/script/UpgradeFactoryImpl.s.sol +2 -2
- package/src/BaseCoin.sol +190 -0
- package/src/Coin.sol +87 -202
- package/src/CoinV4.sol +121 -0
- package/src/ZoraFactoryImpl.sol +208 -36
- package/{script → src/deployment}/CoinsDeployerBase.sol +111 -17
- package/src/hooks/ZoraV4CoinHook.sol +212 -0
- package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
- package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +140 -0
- 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 +67 -28
- 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 +179 -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 +128 -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 +122 -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 +777 -0
- package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +53 -16
- package/test/Factory.t.sol +80 -47
- package/test/MultiOwnable.t.sol +6 -3
- package/test/Upgrades.t.sol +97 -5
- package/test/mocks/MockERC20.sol +12 -0
- package/test/utils/BaseTest.sol +162 -57
- 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/hooks/BuySupplyWithSwapRouterHook.sol +0 -78
- package/src/libs/CoinLegacy.sol +0 -48
- package/src/libs/CoinLegacyMarket.sol +0 -182
package/test/utils/BaseTest.sol
CHANGED
|
@@ -8,10 +8,12 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
|
8
8
|
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
|
|
9
9
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
10
10
|
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
11
|
-
|
|
11
|
+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
12
|
+
import {IZoraFactory} from "../../src/interfaces/IZoraFactory.sol";
|
|
12
13
|
import {ZoraFactoryImpl} from "../../src/ZoraFactoryImpl.sol";
|
|
13
14
|
import {ZoraFactory} from "../../src/proxy/ZoraFactory.sol";
|
|
14
15
|
import {Coin} from "../../src/Coin.sol";
|
|
16
|
+
import {CoinV4} from "../../src/CoinV4.sol";
|
|
15
17
|
import {MultiOwnable} from "../../src/utils/MultiOwnable.sol";
|
|
16
18
|
import {ICoin} from "../../src/interfaces/ICoin.sol";
|
|
17
19
|
import {IERC7572} from "../../src/interfaces/IERC7572.sol";
|
|
@@ -24,17 +26,35 @@ import {IUniswapV3Pool} from "../../src/interfaces/IUniswapV3Pool.sol";
|
|
|
24
26
|
import {IProtocolRewards} from "../../src/interfaces/IProtocolRewards.sol";
|
|
25
27
|
import {ProtocolRewards} from "../utils/ProtocolRewards.sol";
|
|
26
28
|
import {MarketConstants} from "../../src/libs/MarketConstants.sol";
|
|
29
|
+
import {CoinConfigurationVersions} from "../../src/libs/CoinConfigurationVersions.sol";
|
|
30
|
+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
31
|
+
import {ZoraV4CoinHook} from "../../src/hooks/ZoraV4CoinHook.sol";
|
|
32
|
+
import {HooksDeployment} from "../../src/libs/HooksDeployment.sol";
|
|
33
|
+
import {CoinConstants} from "../../src/libs/CoinConstants.sol";
|
|
34
|
+
import {ProxyShim} from "./ProxyShim.sol";
|
|
35
|
+
import {ICoinV4} from "../../src/interfaces/ICoinV4.sol";
|
|
36
|
+
import {UniV4SwapHelper} from "../../src/libs/UniV4SwapHelper.sol";
|
|
37
|
+
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
|
|
38
|
+
import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
|
|
39
|
+
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
|
|
40
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
41
|
+
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
|
|
42
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
27
43
|
|
|
28
44
|
contract BaseTest is Test {
|
|
29
45
|
using stdStorage for StdStorage;
|
|
30
46
|
|
|
31
|
-
address internal constant PROTOCOL_REWARDS = 0x7777777F279eba3d3Ad8F4E708545291A6fDBA8B;
|
|
32
47
|
address internal constant WETH_ADDRESS = 0x4200000000000000000000000000000000000006;
|
|
33
48
|
address internal constant V3_FACTORY = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD;
|
|
34
49
|
address internal constant NONFUNGIBLE_POSITION_MANAGER = 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1;
|
|
35
50
|
address internal constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481;
|
|
36
51
|
address internal constant DOPPLER_AIRLOCK = 0x660eAaEdEBc968f8f3694354FA8EC0b4c5Ba8D12;
|
|
37
52
|
address internal constant USDC_ADDRESS = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
|
|
53
|
+
address internal constant V4_POOL_MANAGER = 0x498581fF718922c3f8e6A244956aF099B2652b2b;
|
|
54
|
+
address internal constant V4_POSITION_MANAGER = 0x7C5f5A4bBd8fD63184577525326123B519429bDc;
|
|
55
|
+
address internal constant V4_PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
|
|
56
|
+
address internal constant V4_QUOTER = 0x0d5e0F971ED27FBfF6c2837bf31316121532048D;
|
|
57
|
+
address internal constant UNIVERSAL_ROUTER = 0x6fF5693b99212Da76ad316178A184AB56D299b43;
|
|
38
58
|
int24 internal constant USDC_TICK_LOWER = 57200;
|
|
39
59
|
|
|
40
60
|
struct Users {
|
|
@@ -54,15 +74,125 @@ contract BaseTest is Test {
|
|
|
54
74
|
ProtocolRewards internal protocolRewards;
|
|
55
75
|
IUniswapV3Factory internal v3Factory;
|
|
56
76
|
INonfungiblePositionManager internal nonfungiblePositionManager;
|
|
77
|
+
IPermit2 internal permit2;
|
|
78
|
+
IUniversalRouter internal router;
|
|
79
|
+
|
|
57
80
|
ISwapRouter internal swapRouter;
|
|
58
81
|
IAirlock internal airlock;
|
|
59
82
|
Users internal users;
|
|
60
83
|
|
|
61
|
-
Coin internal
|
|
84
|
+
Coin internal coinV3Impl;
|
|
85
|
+
CoinV4 internal coinV4Impl;
|
|
62
86
|
ZoraFactoryImpl internal factoryImpl;
|
|
63
|
-
|
|
87
|
+
IZoraFactory internal factory;
|
|
88
|
+
ZoraV4CoinHook internal zoraV4CoinHook;
|
|
64
89
|
Coin internal coin;
|
|
90
|
+
|
|
65
91
|
IUniswapV3Pool internal pool;
|
|
92
|
+
int24 internal constant DEFAULT_DISCOVERY_TICK_LOWER = CoinConstants.DEFAULT_DISCOVERY_TICK_LOWER;
|
|
93
|
+
int24 internal constant DEFAULT_DISCOVERY_TICK_UPPER = CoinConstants.DEFAULT_DISCOVERY_TICK_UPPER;
|
|
94
|
+
uint16 internal constant DEFAULT_NUM_DISCOVERY_POSITIONS = CoinConstants.DEFAULT_NUM_DISCOVERY_POSITIONS;
|
|
95
|
+
uint256 internal constant DEFAULT_DISCOVERY_SUPPLY_SHARE = CoinConstants.DEFAULT_DISCOVERY_SUPPLY_SHARE;
|
|
96
|
+
|
|
97
|
+
function _deployCoin() internal {
|
|
98
|
+
bytes memory poolConfig_ = _generatePoolConfig(
|
|
99
|
+
CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
|
|
100
|
+
address(weth),
|
|
101
|
+
DEFAULT_DISCOVERY_TICK_LOWER,
|
|
102
|
+
DEFAULT_DISCOVERY_TICK_UPPER,
|
|
103
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
104
|
+
DEFAULT_DISCOVERY_SUPPLY_SHARE
|
|
105
|
+
);
|
|
106
|
+
vm.prank(users.creator);
|
|
107
|
+
(address coinAddress, ) = factory.deploy(
|
|
108
|
+
users.creator,
|
|
109
|
+
_getDefaultOwners(),
|
|
110
|
+
"https://test.com",
|
|
111
|
+
"Testcoin",
|
|
112
|
+
"TEST",
|
|
113
|
+
poolConfig_,
|
|
114
|
+
users.platformReferrer,
|
|
115
|
+
0
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
coin = Coin(payable(coinAddress));
|
|
119
|
+
pool = IUniswapV3Pool(coin.poolAddress());
|
|
120
|
+
|
|
121
|
+
vm.label(address(coin), "COIN");
|
|
122
|
+
vm.label(address(pool), "POOL");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function _deployCoinUSDCPair() internal {
|
|
126
|
+
bytes memory poolConfig_ = _generatePoolConfig(
|
|
127
|
+
CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
|
|
128
|
+
USDC_ADDRESS,
|
|
129
|
+
DEFAULT_DISCOVERY_TICK_LOWER,
|
|
130
|
+
DEFAULT_DISCOVERY_TICK_UPPER,
|
|
131
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
132
|
+
DEFAULT_DISCOVERY_SUPPLY_SHARE
|
|
133
|
+
);
|
|
134
|
+
vm.prank(users.creator);
|
|
135
|
+
(address coinAddress, ) = factory.deploy(
|
|
136
|
+
users.creator,
|
|
137
|
+
_getDefaultOwners(),
|
|
138
|
+
"https://test.com",
|
|
139
|
+
"Testcoin",
|
|
140
|
+
"TEST",
|
|
141
|
+
poolConfig_,
|
|
142
|
+
users.platformReferrer,
|
|
143
|
+
0
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
coin = Coin(payable(coinAddress));
|
|
147
|
+
pool = IUniswapV3Pool(coin.poolAddress());
|
|
148
|
+
|
|
149
|
+
vm.label(address(coin), "COIN");
|
|
150
|
+
vm.label(address(pool), "POOL");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _swapSomeCurrencyForCoin(ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
|
|
154
|
+
uint128 minAmountOut = uint128(0);
|
|
155
|
+
|
|
156
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
157
|
+
currency,
|
|
158
|
+
amountIn,
|
|
159
|
+
address(_coin),
|
|
160
|
+
minAmountOut,
|
|
161
|
+
_coin.getPoolKey(),
|
|
162
|
+
bytes("")
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
vm.startPrank(trader);
|
|
166
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
|
|
167
|
+
|
|
168
|
+
// Execute the swap
|
|
169
|
+
uint256 deadline = block.timestamp + 20;
|
|
170
|
+
router.execute(commands, inputs, deadline);
|
|
171
|
+
|
|
172
|
+
vm.stopPrank();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function _swapSomeCoinForCurrency(ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
|
|
176
|
+
uint128 minAmountOut = uint128(0);
|
|
177
|
+
|
|
178
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
179
|
+
address(_coin),
|
|
180
|
+
amountIn,
|
|
181
|
+
currency,
|
|
182
|
+
minAmountOut,
|
|
183
|
+
_coin.getPoolKey(),
|
|
184
|
+
bytes("")
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
vm.startPrank(trader);
|
|
188
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(_coin), amountIn, uint48(block.timestamp + 1 days));
|
|
189
|
+
|
|
190
|
+
// Execute the swap
|
|
191
|
+
uint256 deadline = block.timestamp + 20;
|
|
192
|
+
router.execute(commands, inputs, deadline);
|
|
193
|
+
|
|
194
|
+
vm.stopPrank();
|
|
195
|
+
}
|
|
66
196
|
|
|
67
197
|
function setUp() public virtual {
|
|
68
198
|
setUpWithBlockNumber(28415528);
|
|
@@ -78,7 +208,8 @@ contract BaseTest is Test {
|
|
|
78
208
|
swapRouter = ISwapRouter(SWAP_ROUTER);
|
|
79
209
|
airlock = IAirlock(DOPPLER_AIRLOCK);
|
|
80
210
|
protocolRewards = new ProtocolRewards();
|
|
81
|
-
|
|
211
|
+
permit2 = IPermit2(V4_PERMIT2);
|
|
212
|
+
router = IUniversalRouter(UNIVERSAL_ROUTER);
|
|
82
213
|
users = Users({
|
|
83
214
|
factoryOwner: makeAddr("factoryOwner"),
|
|
84
215
|
feeRecipient: makeAddr("feeRecipient"),
|
|
@@ -90,11 +221,21 @@ contract BaseTest is Test {
|
|
|
90
221
|
tradeReferrer: makeAddr("tradeReferrer")
|
|
91
222
|
});
|
|
92
223
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
224
|
+
address[] memory trustedMessageSenders = new address[](2);
|
|
225
|
+
trustedMessageSenders[0] = UNIVERSAL_ROUTER;
|
|
226
|
+
trustedMessageSenders[1] = V4_POSITION_MANAGER;
|
|
96
227
|
|
|
97
|
-
|
|
228
|
+
ProxyShim mockUpgradeableImpl = new ProxyShim();
|
|
229
|
+
factory = IZoraFactory(address(new ZoraFactory(address(mockUpgradeableImpl))));
|
|
230
|
+
zoraV4CoinHook = ZoraV4CoinHook(address(HooksDeployment.deployZoraV4CoinHookFromContract(V4_POOL_MANAGER, address(factory), trustedMessageSenders)));
|
|
231
|
+
coinV3Impl = new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, V3_FACTORY, SWAP_ROUTER, DOPPLER_AIRLOCK);
|
|
232
|
+
coinV4Impl = new CoinV4(users.feeRecipient, address(protocolRewards), IPoolManager(V4_POOL_MANAGER), DOPPLER_AIRLOCK, zoraV4CoinHook);
|
|
233
|
+
factoryImpl = new ZoraFactoryImpl(address(coinV3Impl), address(coinV4Impl));
|
|
234
|
+
UUPSUpgradeable(address(factory)).upgradeToAndCall(address(factoryImpl), "");
|
|
235
|
+
factory = IZoraFactory(address(factory));
|
|
236
|
+
// factory = ZoraFactoryImpl(address(new ZoraFactory(address(factoryImpl))));
|
|
237
|
+
|
|
238
|
+
ZoraFactoryImpl(address(factory)).initialize(users.factoryOwner);
|
|
98
239
|
|
|
99
240
|
vm.label(address(factory), "ZORA_FACTORY");
|
|
100
241
|
vm.label(address(protocolRewards), "PROTOCOL_REWARDS");
|
|
@@ -120,54 +261,6 @@ contract BaseTest is Test {
|
|
|
120
261
|
uint256 protocol;
|
|
121
262
|
}
|
|
122
263
|
|
|
123
|
-
function _deployCoin() internal {
|
|
124
|
-
address[] memory owners = new address[](1);
|
|
125
|
-
owners[0] = users.creator;
|
|
126
|
-
|
|
127
|
-
vm.prank(users.creator);
|
|
128
|
-
(address coinAddress, ) = factory.deploy(
|
|
129
|
-
users.creator,
|
|
130
|
-
owners,
|
|
131
|
-
"https://test.com",
|
|
132
|
-
"Testcoin",
|
|
133
|
-
"TEST",
|
|
134
|
-
users.platformReferrer,
|
|
135
|
-
address(weth),
|
|
136
|
-
MarketConstants.LP_TICK_LOWER_WETH,
|
|
137
|
-
0
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
coin = Coin(payable(coinAddress));
|
|
141
|
-
pool = IUniswapV3Pool(coin.poolAddress());
|
|
142
|
-
|
|
143
|
-
vm.label(address(coin), "COIN");
|
|
144
|
-
vm.label(address(pool), "POOL");
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function _deployCoinUSDCPair() internal {
|
|
148
|
-
address[] memory owners = new address[](1);
|
|
149
|
-
owners[0] = users.creator;
|
|
150
|
-
|
|
151
|
-
vm.prank(users.creator);
|
|
152
|
-
(address coinAddress, ) = factory.deploy(
|
|
153
|
-
users.creator,
|
|
154
|
-
owners,
|
|
155
|
-
"https://testusdccoin.com",
|
|
156
|
-
"Testusdccoin",
|
|
157
|
-
"TESTUSDCCOIN",
|
|
158
|
-
users.platformReferrer,
|
|
159
|
-
USDC_ADDRESS,
|
|
160
|
-
USDC_TICK_LOWER,
|
|
161
|
-
0
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
coin = Coin(payable(coinAddress));
|
|
165
|
-
pool = IUniswapV3Pool(coin.poolAddress());
|
|
166
|
-
|
|
167
|
-
vm.label(address(coin), "COIN");
|
|
168
|
-
vm.label(address(pool), "POOL");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
264
|
function _calculateTradeRewards(uint256 ethAmount) internal pure returns (TradeRewards memory) {
|
|
172
265
|
return
|
|
173
266
|
TradeRewards({
|
|
@@ -207,6 +300,18 @@ contract BaseTest is Test {
|
|
|
207
300
|
return airlock.owner();
|
|
208
301
|
}
|
|
209
302
|
|
|
303
|
+
function _generatePoolConfig(address currency_) internal pure returns (bytes memory) {
|
|
304
|
+
return
|
|
305
|
+
_generatePoolConfig(
|
|
306
|
+
CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
|
|
307
|
+
currency_,
|
|
308
|
+
DEFAULT_DISCOVERY_TICK_LOWER,
|
|
309
|
+
DEFAULT_DISCOVERY_TICK_UPPER,
|
|
310
|
+
DEFAULT_NUM_DISCOVERY_POSITIONS,
|
|
311
|
+
DEFAULT_DISCOVERY_SUPPLY_SHARE
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
210
315
|
function _generatePoolConfig(
|
|
211
316
|
uint8 version_,
|
|
212
317
|
address currency_,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {BaseTest} from "../utils/BaseTest.sol";
|
|
5
|
+
import {DeployedCoinVersionLookup} from "../../src/utils/DeployedCoinVersionLookup.sol";
|
|
6
|
+
|
|
7
|
+
contract TestDeployedCoinVersionLookupImplementation is DeployedCoinVersionLookup {
|
|
8
|
+
function setVersionForTesting(address coin, uint8 version) external {
|
|
9
|
+
_setVersionForDeployedCoin(coin, version);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @title Mock implementation with different namespace
|
|
15
|
+
* @dev Used to verify that different namespaces don't collide
|
|
16
|
+
*/
|
|
17
|
+
contract DifferentNamespaceVersionLookup {
|
|
18
|
+
/// @custom:storage-location erc7201:different.namespace
|
|
19
|
+
struct DeployedCoinVersionStorage {
|
|
20
|
+
mapping(address => uint8) deployedCoinWithVersion;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// keccak256(abi.encode(uint256(keccak256("different.namespace")) - 1)) & ~bytes32(uint256(0xff))
|
|
24
|
+
bytes32 private constant DEPLOYED_COIN_VERSION_STORAGE_LOCATION = 0xf0ec9c7ea8b861b539967dd0659fb8887a9724eca55e932839a2a8e01f50c400;
|
|
25
|
+
|
|
26
|
+
function _getDeployedCoinVersionStorage() private pure returns (DeployedCoinVersionStorage storage $) {
|
|
27
|
+
assembly {
|
|
28
|
+
$.slot := DEPLOYED_COIN_VERSION_STORAGE_LOCATION
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getVersionForDeployedCoin(address coin) public view returns (uint8) {
|
|
33
|
+
return _getDeployedCoinVersionStorage().deployedCoinWithVersion[coin];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setVersionForTesting(address coin, uint8 version) external {
|
|
37
|
+
_getDeployedCoinVersionStorage().deployedCoinWithVersion[coin] = version;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
contract DeployedCoinVersionLookupTest is BaseTest {
|
|
42
|
+
TestDeployedCoinVersionLookupImplementation public versionLookup;
|
|
43
|
+
DifferentNamespaceVersionLookup public differentNamespaceLookup;
|
|
44
|
+
address public testCoin1;
|
|
45
|
+
address public testCoin2;
|
|
46
|
+
address public testContractAddress;
|
|
47
|
+
|
|
48
|
+
function setUp() public override {
|
|
49
|
+
super.setUp();
|
|
50
|
+
versionLookup = new TestDeployedCoinVersionLookupImplementation();
|
|
51
|
+
differentNamespaceLookup = new DifferentNamespaceVersionLookup();
|
|
52
|
+
testCoin1 = makeAddr("testCoin1");
|
|
53
|
+
testCoin2 = makeAddr("testCoin2");
|
|
54
|
+
testContractAddress = makeAddr("testContractAddress");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function test_getAndSetVersionForDeployedCoin() public {
|
|
58
|
+
// Default version should be 0
|
|
59
|
+
assertEq(versionLookup.getVersionForDeployedCoin(testCoin1), 0);
|
|
60
|
+
|
|
61
|
+
// Set version and verify
|
|
62
|
+
versionLookup.setVersionForTesting(testCoin1, 1);
|
|
63
|
+
assertEq(versionLookup.getVersionForDeployedCoin(testCoin1), 1);
|
|
64
|
+
|
|
65
|
+
// Set version for a different coin
|
|
66
|
+
versionLookup.setVersionForTesting(testCoin2, 2);
|
|
67
|
+
assertEq(versionLookup.getVersionForDeployedCoin(testCoin2), 2);
|
|
68
|
+
|
|
69
|
+
// First coin's version should remain unchanged
|
|
70
|
+
assertEq(versionLookup.getVersionForDeployedCoin(testCoin1), 1);
|
|
71
|
+
|
|
72
|
+
// Update version and verify
|
|
73
|
+
versionLookup.setVersionForTesting(testCoin1, 3);
|
|
74
|
+
assertEq(versionLookup.getVersionForDeployedCoin(testCoin1), 3);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function test_differentNamespaceIndependence() public {
|
|
78
|
+
// First deploy the original implementation at a fixed address
|
|
79
|
+
TestDeployedCoinVersionLookupImplementation originalImpl = new TestDeployedCoinVersionLookupImplementation();
|
|
80
|
+
bytes memory originalBytecode = address(originalImpl).code;
|
|
81
|
+
|
|
82
|
+
// Deploy a different implementation
|
|
83
|
+
DifferentNamespaceVersionLookup differentImpl = new DifferentNamespaceVersionLookup();
|
|
84
|
+
bytes memory differentBytecode = address(differentImpl).code;
|
|
85
|
+
|
|
86
|
+
// Etch the original implementation to the test address
|
|
87
|
+
vm.etch(testContractAddress, originalBytecode);
|
|
88
|
+
|
|
89
|
+
// Test setting values with the first implementation
|
|
90
|
+
TestDeployedCoinVersionLookupImplementation(testContractAddress).setVersionForTesting(testCoin1, 42);
|
|
91
|
+
assertEq(TestDeployedCoinVersionLookupImplementation(testContractAddress).getVersionForDeployedCoin(testCoin1), 42);
|
|
92
|
+
|
|
93
|
+
// Save the bytecode location for the first implementation
|
|
94
|
+
bytes32 firstSlot = vm.load(
|
|
95
|
+
testContractAddress,
|
|
96
|
+
bytes32(uint256(keccak256(abi.encode(testCoin1, 0x9a79df0b86f39d0543c14aee714123562f798115071e932933bcc3e29cc86f00))))
|
|
97
|
+
);
|
|
98
|
+
assertEq(uint256(firstSlot), 42);
|
|
99
|
+
|
|
100
|
+
// Now replace the code with the different namespace implementation
|
|
101
|
+
vm.etch(testContractAddress, differentBytecode);
|
|
102
|
+
|
|
103
|
+
// Set a value with the different implementation
|
|
104
|
+
DifferentNamespaceVersionLookup(testContractAddress).setVersionForTesting(testCoin1, 99);
|
|
105
|
+
|
|
106
|
+
// This should use a different storage slot, so it shouldn't affect the original value
|
|
107
|
+
assertEq(DifferentNamespaceVersionLookup(testContractAddress).getVersionForDeployedCoin(testCoin1), 99);
|
|
108
|
+
|
|
109
|
+
// Verify the original storage slot still has the original value
|
|
110
|
+
bytes32 secondSlot = vm.load(
|
|
111
|
+
testContractAddress,
|
|
112
|
+
bytes32(uint256(keccak256(abi.encode(testCoin1, 0xf0ec9c7ea8b861b539967dd0659fb8887a9724eca55e932839a2a8e01f50c400))))
|
|
113
|
+
);
|
|
114
|
+
assertEq(uint256(secondSlot), 99);
|
|
115
|
+
|
|
116
|
+
// Switch back to the original implementation to verify its storage is unchanged
|
|
117
|
+
vm.etch(testContractAddress, originalBytecode);
|
|
118
|
+
assertEq(TestDeployedCoinVersionLookupImplementation(testContractAddress).getVersionForDeployedCoin(testCoin1), 42);
|
|
119
|
+
|
|
120
|
+
// Change the value in the original implementation
|
|
121
|
+
TestDeployedCoinVersionLookupImplementation(testContractAddress).setVersionForTesting(testCoin1, 123);
|
|
122
|
+
|
|
123
|
+
// Switch back to the different namespace implementation to verify its storage is unchanged
|
|
124
|
+
vm.etch(testContractAddress, differentBytecode);
|
|
125
|
+
assertEq(DifferentNamespaceVersionLookup(testContractAddress).getVersionForDeployedCoin(testCoin1), 99);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {ZoraV4CoinHook} from "../../src/hooks/ZoraV4CoinHook.sol";
|
|
5
|
+
import {IPoolManager, PoolKey} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
6
|
+
import {IDeployedCoinVersionLookup} from "../../src/interfaces/IDeployedCoinVersionLookup.sol";
|
|
7
|
+
import {IHasRewardsRecipients} from "../../src/interfaces/ICoin.sol";
|
|
8
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
9
|
+
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
|
|
10
|
+
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
11
|
+
import {CoinCommon} from "../../src/libs/CoinCommon.sol";
|
|
12
|
+
import {V4Liquidity} from "../../src/libs/V4Liquidity.sol";
|
|
13
|
+
import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol";
|
|
14
|
+
import {ICoinV4, IHasSwapPath} from "../../src/interfaces/ICoinV4.sol";
|
|
15
|
+
import {UniV4SwapToCurrency} from "../../src/libs/UniV4SwapToCurrency.sol";
|
|
16
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
17
|
+
import {CoinRewardsV4} from "../../src/libs/CoinRewardsV4.sol";
|
|
18
|
+
|
|
19
|
+
/// @dev Test util - meant to be able to etched where a normal zora hook is, to gather the fees from swaps but not distribute them
|
|
20
|
+
contract FeeEstimatorHook is ZoraV4CoinHook {
|
|
21
|
+
struct FeeEstimatorState {
|
|
22
|
+
uint128 fees0;
|
|
23
|
+
uint128 fees1;
|
|
24
|
+
Currency afterSwapCurrency;
|
|
25
|
+
uint128 afterSwapCurrencyAmount;
|
|
26
|
+
BalanceDelta lastDelta;
|
|
27
|
+
SwapParams lastSwapParams;
|
|
28
|
+
uint256 currencyBalanceChange;
|
|
29
|
+
uint256 coinBalanceChange;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor(IPoolManager _poolManager, IDeployedCoinVersionLookup _coinVersionLookup) ZoraV4CoinHook(_poolManager, _coinVersionLookup, new address[](0)) {}
|
|
33
|
+
|
|
34
|
+
FeeEstimatorState public feeState;
|
|
35
|
+
|
|
36
|
+
function getFeeState() public view returns (FeeEstimatorState memory) {
|
|
37
|
+
return feeState;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _afterSwap(
|
|
41
|
+
address,
|
|
42
|
+
PoolKey calldata key,
|
|
43
|
+
SwapParams calldata params,
|
|
44
|
+
BalanceDelta _delta,
|
|
45
|
+
bytes calldata
|
|
46
|
+
) internal override returns (bytes4, int128) {
|
|
47
|
+
bytes32 poolKeyHash = CoinCommon.hashPoolKey(key);
|
|
48
|
+
|
|
49
|
+
// get the coin address and positions for the pool key; they must have been set in the afterInitialize callback
|
|
50
|
+
address coin = poolCoins[poolKeyHash].coin;
|
|
51
|
+
require(coin != address(0), NoCoinForHook(key));
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
uint256 coinBalanceBefore = IERC20(coin).balanceOf(address(this));
|
|
55
|
+
uint256 currencyBalanceBefore = IERC20(ICoinV4(coin).currency()).balanceOf(address(this));
|
|
56
|
+
|
|
57
|
+
IHasSwapPath.PayoutSwapPath memory payoutSwapPath = IHasSwapPath(coin).getPayoutSwapPath(coinVersionLookup);
|
|
58
|
+
|
|
59
|
+
int128 fee0;
|
|
60
|
+
int128 fee1;
|
|
61
|
+
|
|
62
|
+
(fee0, fee1, feeState.afterSwapCurrency, feeState.afterSwapCurrencyAmount) = CoinRewardsV4.collectFeesAndConvertToPayout(
|
|
63
|
+
poolManager,
|
|
64
|
+
key,
|
|
65
|
+
poolCoins[poolKeyHash].positions,
|
|
66
|
+
payoutSwapPath
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
feeState.fees0 += uint128(fee0);
|
|
70
|
+
feeState.fees1 += uint128(fee1);
|
|
71
|
+
|
|
72
|
+
uint256 coinBalanceAfter = IERC20(coin).balanceOf(address(this));
|
|
73
|
+
uint256 currencyBalanceAfter = IERC20(ICoinV4(coin).currency()).balanceOf(address(this));
|
|
74
|
+
|
|
75
|
+
feeState.coinBalanceChange = coinBalanceAfter - coinBalanceBefore;
|
|
76
|
+
feeState.currencyBalanceChange = currencyBalanceAfter - currencyBalanceBefore;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
feeState.lastDelta = _delta;
|
|
80
|
+
feeState.lastSwapParams = params;
|
|
81
|
+
|
|
82
|
+
return (BaseHook.afterSwap.selector, 0);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.18;
|
|
3
|
+
|
|
4
|
+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
5
|
+
|
|
6
|
+
/// Used to deploy the factory before we know the impl address
|
|
7
|
+
contract ProxyShim is UUPSUpgradeable {
|
|
8
|
+
address immutable canUpgrade;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
canUpgrade = msg.sender;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function _authorizeUpgrade(address) internal view override {
|
|
15
|
+
require(msg.sender == canUpgrade, "not authorized");
|
|
16
|
+
}
|
|
17
|
+
}
|
package/wagmi.config.ts
CHANGED
|
@@ -10,9 +10,13 @@ export default defineConfig({
|
|
|
10
10
|
},
|
|
11
11
|
include: [
|
|
12
12
|
"Coin",
|
|
13
|
+
"CoinV4",
|
|
13
14
|
"ZoraFactoryImpl",
|
|
14
15
|
"IUniswapV3Pool",
|
|
15
16
|
"BuySupplyWithSwapRouterHook",
|
|
17
|
+
"IPoolConfigEncoding",
|
|
18
|
+
"IUniversalRouter",
|
|
19
|
+
"IPermit2",
|
|
16
20
|
].map((contractName) => `${contractName}.json`),
|
|
17
21
|
}),
|
|
18
22
|
],
|
package/.env
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ALCHEMY_KEY=Ebx4-biYR4T-p-1BOId9DozVRrr3nHu4
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> @zoralabs/coins@0.8.0 update-contract-version /Users/danovedzora/source/zora-protocol/packages/coins
|
|
4
|
-
> pnpm exec update-contract-version
|
|
5
|
-
|
|
6
|
-
updating contract version to 0.8.0
|
|
7
|
-
generated contract version code: // This file is automatically generated by code; do not manually update
|
|
8
|
-
// SPDX-License-Identifier: MIT
|
|
9
|
-
pragma solidity ^0.8.23;
|
|
10
|
-
|
|
11
|
-
import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersionedContract.sol";
|
|
12
|
-
|
|
13
|
-
/// @title ContractVersionBase
|
|
14
|
-
/// @notice Base contract for versioning contracts
|
|
15
|
-
contract ContractVersionBase is IVersionedContract {
|
|
16
|
-
/// @notice The version of the contract
|
|
17
|
-
function contractVersion() external pure override returns (string memory) {
|
|
18
|
-
return "0.8.0";
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
writing file to /Users/danovedzora/source/zora-protocol/packages/coins/src/version/ContractVersionBase.sol
|
package/abis/CoinSetupV3.json
DELETED
package/abis/HookDeployer.json
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"type": "function",
|
|
4
|
-
"name": "deployZoraV4CoinHookFromContract",
|
|
5
|
-
"inputs": [
|
|
6
|
-
{
|
|
7
|
-
"name": "poolManager",
|
|
8
|
-
"type": "address",
|
|
9
|
-
"internalType": "address"
|
|
10
|
-
}
|
|
11
|
-
],
|
|
12
|
-
"outputs": [
|
|
13
|
-
{
|
|
14
|
-
"name": "hook",
|
|
15
|
-
"type": "address",
|
|
16
|
-
"internalType": "contract IHooks"
|
|
17
|
-
}
|
|
18
|
-
],
|
|
19
|
-
"stateMutability": "nonpayable"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"type": "error",
|
|
23
|
-
"name": "Create2EmptyBytecode",
|
|
24
|
-
"inputs": []
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"type": "error",
|
|
28
|
-
"name": "Create2FailedDeployment",
|
|
29
|
-
"inputs": []
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"type": "error",
|
|
33
|
-
"name": "Create2InsufficientBalance",
|
|
34
|
-
"inputs": [
|
|
35
|
-
{
|
|
36
|
-
"name": "balance",
|
|
37
|
-
"type": "uint256",
|
|
38
|
-
"internalType": "uint256"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"name": "needed",
|
|
42
|
-
"type": "uint256",
|
|
43
|
-
"internalType": "uint256"
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"type": "error",
|
|
49
|
-
"name": "HookNotDeployed",
|
|
50
|
-
"inputs": []
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"type": "error",
|
|
54
|
-
"name": "InvalidHookAddress",
|
|
55
|
-
"inputs": [
|
|
56
|
-
{
|
|
57
|
-
"name": "expected",
|
|
58
|
-
"type": "address",
|
|
59
|
-
"internalType": "address"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"name": "actual",
|
|
63
|
-
"type": "address",
|
|
64
|
-
"internalType": "address"
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
]
|
package/abis/IHookDeployer.json
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"type": "function",
|
|
4
|
-
"name": "deployZoraV4CoinHookFromContract",
|
|
5
|
-
"inputs": [
|
|
6
|
-
{
|
|
7
|
-
"name": "poolManager",
|
|
8
|
-
"type": "address",
|
|
9
|
-
"internalType": "address"
|
|
10
|
-
}
|
|
11
|
-
],
|
|
12
|
-
"outputs": [
|
|
13
|
-
{
|
|
14
|
-
"name": "hook",
|
|
15
|
-
"type": "address",
|
|
16
|
-
"internalType": "contract IHooks"
|
|
17
|
-
}
|
|
18
|
-
],
|
|
19
|
-
"stateMutability": "nonpayable"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"type": "error",
|
|
23
|
-
"name": "HookNotDeployed",
|
|
24
|
-
"inputs": []
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"type": "error",
|
|
28
|
-
"name": "InvalidHookAddress",
|
|
29
|
-
"inputs": [
|
|
30
|
-
{
|
|
31
|
-
"name": "expected",
|
|
32
|
-
"type": "address",
|
|
33
|
-
"internalType": "address"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"name": "actual",
|
|
37
|
-
"type": "address",
|
|
38
|
-
"internalType": "address"
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
]
|