@zoralabs/coins 2.4.0 → 2.5.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/.abi-stability +923 -0
- package/.turbo/turbo-build$colon$js.log +110 -116
- package/CHANGELOG.md +25 -0
- package/abis/Address.json +0 -16
- package/abis/BaseCoin.json +18 -0
- package/abis/BuySupplyWithSwapRouterHook.json +0 -27
- package/abis/BuySupplyWithV4SwapHook.json +0 -32
- package/abis/Clones.json +1 -1
- package/abis/CoinDopplerMultiCurve.json +109 -0
- package/abis/ContentCoin.json +18 -0
- package/abis/Create2.json +0 -21
- package/abis/CreatorCoin.json +18 -0
- package/abis/ERC1967Proxy.json +1 -1
- package/abis/ERC1967Utils.json +0 -45
- package/abis/{UpgradeCoinImpl.json → Errors.json} +14 -10
- package/abis/{MockERC20.json → IERC1363.json} +134 -104
- package/abis/IERC1967.json +47 -0
- package/abis/IERC20.json +0 -36
- package/abis/IHasCreationInfo.json +20 -0
- package/abis/IProtocolRewards.json +0 -258
- package/abis/{Script.json → ISupportsLimitOrderFill.json} +2 -2
- package/abis/IZoraLimitOrderBookCoinsInterface.json +67 -0
- package/abis/IZoraV4CoinHook.json +10 -0
- package/abis/ProxyShim.json +15 -16
- package/abis/SafeCast.json +51 -0
- package/abis/{AddressConstants.json → SafeCast160.json} +1 -1
- package/abis/Strings.json +10 -0
- package/abis/UUPSUpgradeable.json +1 -1
- package/abis/V3ToV4SwapLib.json +28 -0
- package/abis/ZoraFactory.json +1 -1
- package/abis/ZoraFactoryImpl.json +22 -6
- package/abis/ZoraV4CoinHook.json +20 -48
- package/dist/index.cjs +980 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +978 -41
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +1501 -76
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +981 -44
- package/package.json +11 -9
- package/remappings.txt +2 -1
- package/src/BaseCoin.sol +32 -2
- package/src/ZoraFactoryImpl.sol +8 -0
- package/src/deployment/ForkedCoinsAddresses.sol +54 -0
- package/src/hooks/ZoraV4CoinHook.sol +131 -20
- package/src/hooks/deployment/BuySupplyWithV4SwapHook.sol +20 -142
- package/src/interfaces/IHasCreationInfo.sol +12 -0
- package/src/interfaces/ISupportsLimitOrderFill.sol +11 -0
- package/src/interfaces/IZoraLimitOrderBookCoinsInterface.sol +21 -0
- package/src/interfaces/IZoraV4CoinHook.sol +6 -0
- package/src/libs/CoinConstants.sol +22 -0
- package/src/libs/CoinDopplerMultiCurve.sol +1 -1
- package/src/libs/CoinRewardsV4.sol +0 -1
- package/src/libs/CoinSetup.sol +7 -1
- package/src/libs/HooksDeployment.sol +20 -8
- package/src/libs/UniV4SwapHelper.sol +35 -0
- package/src/libs/V3ToV4SwapLib.sol +265 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/BuySupplyWithV4SwapHook.t.sol +4 -3
- package/test/Coin.t.sol +7 -1
- package/test/CoinUniV4.t.sol +6 -1
- package/test/ContentCoinRewards.t.sol +6 -1
- package/test/CreatorCoin.t.sol +3 -1
- package/test/CreatorCoinRewards.t.sol +4 -1
- package/test/Factory.t.sol +20 -7
- package/test/HooksDeployment.t.sol +16 -3
- package/test/LaunchFee.t.sol +286 -0
- package/test/LiquidityMigration.t.sol +52 -44
- package/test/MultiOwnable.t.sol +2 -1
- package/test/Upgrades.t.sol +110 -81
- package/test/V4Liquidity.t.sol +1 -1
- package/test/mocks/MockSwapRouter.sol +33 -0
- package/test/mocks/MockZoraLimitOrderBook.sol +14 -0
- package/test/utils/BaseTest.sol +14 -448
- package/test/utils/FeeEstimatorHook.sol +6 -2
- package/test/utils/V4TestSetup.sol +595 -0
- package/wagmi.config.ts +1 -1
- package/abis/BaseTest.json +0 -718
- package/abis/DeterministicDeployerAndCaller.json +0 -315
- package/abis/DeterministicUUPSProxyDeployer.json +0 -167
- package/abis/EIP712.json +0 -67
- package/abis/ERC20.json +0 -310
- package/abis/FeeEstimatorHook.json +0 -1938
- package/abis/IERC721.json +0 -287
- package/abis/IERC721Enumerable.json +0 -343
- package/abis/IERC721Metadata.json +0 -332
- package/abis/IERC721TokenReceiver.json +0 -36
- package/abis/IImmutableCreate2Factory.json +0 -93
- package/abis/IMulticall3.json +0 -440
- package/abis/ISafe.json +0 -15
- package/abis/ISymbol.json +0 -15
- package/abis/IUniswapV4Router04.json +0 -484
- package/abis/IUniversalRouter.json +0 -61
- package/abis/IV4Quoter.json +0 -310
- package/abis/ImmutableCreate2FactoryUtils.json +0 -15
- package/abis/LibString.json +0 -7
- package/abis/Math.json +0 -7
- package/abis/MockAirlock.json +0 -39
- package/abis/MockERC721.json +0 -350
- package/abis/ProtocolRewards.json +0 -494
- package/abis/ShortStrings.json +0 -18
- package/abis/SimpleERC20.json +0 -326
- package/abis/StdAssertions.json +0 -379
- package/abis/StdInvariant.json +0 -180
- package/abis/Test.json +0 -570
- package/abis/VmContractHelper235.json +0 -233
- package/abis/VmContractHelper242.json +0 -233
- package/abis/stdError.json +0 -119
- package/abis/stdStorageSafe.json +0 -52
- package/addresses/8453.json +0 -13
- package/addresses/84532.json +0 -10
- package/deterministicConfig/deployerAndCaller.json +0 -5
- package/deterministicConfig/zoraFactory.json +0 -8
- package/script/Deploy.s.sol +0 -23
- package/script/DeployAutoSwapper.s.sol +0 -30
- package/script/DeployDevFactory.s.sol +0 -21
- package/script/DeployPostDeploymentHooks.s.sol +0 -20
- package/script/DeployTrustedMsgSenderLookup.s.sol +0 -20
- package/script/DeployUpgradeGate.s.sol +0 -21
- package/script/GenerateDeterministicParams.s.sol +0 -43
- package/script/PrintRegisterUpgradePath.s.sol +0 -28
- package/script/PrintUpgradeCommand.s.sol +0 -13
- package/script/TestBackingCoinSwap.s.sol +0 -144
- package/script/TestV4Swap.s.sol +0 -133
- package/script/UpgradeCoinImpl.sol +0 -23
- package/script/UpgradeFactoryImpl.s.sol +0 -28
- package/script/UpgradeHooks.s.sol +0 -23
- package/src/deployment/CoinsDeployerBase.sol +0 -297
- /package/{test → src}/utils/ProxyShim.sol +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.13;
|
|
3
3
|
|
|
4
|
-
import "./utils/BaseTest.sol";
|
|
4
|
+
import {BaseTest} from "./utils/BaseTest.sol";
|
|
5
5
|
import {console} from "forge-std/console.sol";
|
|
6
6
|
|
|
7
7
|
import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
|
|
@@ -13,8 +13,11 @@ import {FeeEstimatorHook} from "./utils/FeeEstimatorHook.sol";
|
|
|
13
13
|
import {RewardTestHelpers, RewardBalances} from "./utils/RewardTestHelpers.sol";
|
|
14
14
|
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
15
15
|
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
16
|
+
import {CreatorCoin} from "../src/CreatorCoin.sol";
|
|
16
17
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
18
|
+
import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
|
|
17
19
|
|
|
20
|
+
/// forge-config: default.isolate = true
|
|
18
21
|
contract CreatorCoinRewardsTest is BaseTest {
|
|
19
22
|
CreatorCoin internal creatorCoin;
|
|
20
23
|
|
package/test/Factory.t.sol
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.13;
|
|
3
3
|
|
|
4
|
-
import "./utils/BaseTest.sol";
|
|
4
|
+
import {BaseTest} from "./utils/BaseTest.sol";
|
|
5
5
|
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
6
6
|
import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
|
|
7
7
|
import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
|
|
8
|
+
import {ZoraFactoryImpl} from "../src/ZoraFactoryImpl.sol";
|
|
9
|
+
import {ZoraFactory} from "../src/proxy/ZoraFactory.sol";
|
|
10
|
+
import {ContentCoin} from "../src/ContentCoin.sol";
|
|
11
|
+
import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
|
|
12
|
+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
13
|
+
import {MockZoraLimitOrderBook} from "./mocks/MockZoraLimitOrderBook.sol";
|
|
8
14
|
|
|
9
15
|
contract FactoryTest is BaseTest {
|
|
10
16
|
function setUp() public override {
|
|
@@ -19,9 +25,16 @@ contract FactoryTest is BaseTest {
|
|
|
19
25
|
|
|
20
26
|
// proxy initialization test
|
|
21
27
|
address initialOwner = makeAddr("initialOwner");
|
|
22
|
-
ZoraFactory
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
ZoraFactory newFactory = new ZoraFactory(address(impl));
|
|
29
|
+
|
|
30
|
+
// Add the new factory as an owner of the hook registry so it can register hooks during initialization
|
|
31
|
+
address[] memory newOwners = new address[](1);
|
|
32
|
+
newOwners[0] = address(newFactory);
|
|
33
|
+
vm.prank(users.factoryOwner);
|
|
34
|
+
zoraHookRegistry.addOwners(newOwners);
|
|
35
|
+
|
|
36
|
+
ZoraFactoryImpl(address(newFactory)).initialize(address(initialOwner));
|
|
37
|
+
assertEq(ZoraFactoryImpl(address(newFactory)).owner(), initialOwner);
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
function test_ownable2Step() public {
|
|
@@ -166,9 +179,9 @@ contract FactoryTest is BaseTest {
|
|
|
166
179
|
address[] memory registeredHooks;
|
|
167
180
|
|
|
168
181
|
registeredHooks = zoraHookRegistry.getHookAddresses();
|
|
169
|
-
assertEq(registeredHooks.length,
|
|
182
|
+
assertEq(registeredHooks.length, 1);
|
|
170
183
|
|
|
171
|
-
_deployHooks(); // Deploys new content and creator coin hook addresses
|
|
184
|
+
_deployHooks(address(new MockZoraLimitOrderBook())); // Deploys new content and creator coin hook addresses
|
|
172
185
|
|
|
173
186
|
// Deploy new factory impl with new content and creator coin hook addresses
|
|
174
187
|
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
@@ -177,7 +190,7 @@ contract FactoryTest is BaseTest {
|
|
|
177
190
|
ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
|
|
178
191
|
|
|
179
192
|
registeredHooks = zoraHookRegistry.getHookAddresses();
|
|
180
|
-
assertEq(registeredHooks.length,
|
|
193
|
+
assertEq(registeredHooks.length, 2);
|
|
181
194
|
assertTrue(zoraHookRegistry.isRegisteredHook(address(hook)));
|
|
182
195
|
}
|
|
183
196
|
}
|
|
@@ -10,10 +10,12 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
|
|
|
10
10
|
import {HookUpgradeGate} from "../src/hooks/HookUpgradeGate.sol";
|
|
11
11
|
import {ITrustedMsgSenderProviderLookup} from "../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
12
12
|
import {TrustedSenderTestHelper} from "./utils/TrustedSenderTestHelper.sol";
|
|
13
|
+
import {ZoraHookRegistry} from "../src/hook-registry/ZoraHookRegistry.sol";
|
|
13
14
|
|
|
14
15
|
contract HooksDeploymentTest is Test, ContractAddresses {
|
|
15
16
|
address internal hookUpgradeGate;
|
|
16
17
|
ITrustedMsgSenderProviderLookup internal trustedMsgSenderLookup;
|
|
18
|
+
address internal mockHookRegistry;
|
|
17
19
|
address internal owner;
|
|
18
20
|
address internal nonOwner;
|
|
19
21
|
address internal trustedSender1;
|
|
@@ -30,6 +32,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
30
32
|
nonTrustedSender = makeAddr("nonTrustedSender");
|
|
31
33
|
|
|
32
34
|
hookUpgradeGate = address(new HookUpgradeGate(makeAddr("factoryOwner")));
|
|
35
|
+
mockHookRegistry = makeAddr("mockHookRegistry");
|
|
33
36
|
|
|
34
37
|
// Initialize with one trusted sender
|
|
35
38
|
address[] memory initialTrustedSenders = new address[](1);
|
|
@@ -74,6 +77,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
74
77
|
function test_canDeployContentCoinHookFromScript() public {
|
|
75
78
|
vm.createSelectFork("base", 31653138);
|
|
76
79
|
|
|
80
|
+
address mockOrderFiller = makeAddr("mockOrderFiller");
|
|
77
81
|
address[] memory trustedMessageSenders = new address[](0);
|
|
78
82
|
|
|
79
83
|
ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
|
|
@@ -86,13 +90,17 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
86
90
|
V4_POOL_MANAGER,
|
|
87
91
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
88
92
|
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
89
|
-
hookUpgradeGate
|
|
93
|
+
hookUpgradeGate,
|
|
94
|
+
mockOrderFiller,
|
|
95
|
+
mockHookRegistry
|
|
90
96
|
);
|
|
91
97
|
IHooks hook = HooksDeployment.deployZoraV4CoinHook(
|
|
92
98
|
V4_POOL_MANAGER,
|
|
93
99
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
94
100
|
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
95
101
|
hookUpgradeGate,
|
|
102
|
+
mockOrderFiller,
|
|
103
|
+
mockHookRegistry,
|
|
96
104
|
salt
|
|
97
105
|
);
|
|
98
106
|
|
|
@@ -108,6 +116,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
108
116
|
function test_canDeployCreatorCoinHookFromScript() public {
|
|
109
117
|
vm.createSelectFork("base", 31653138);
|
|
110
118
|
|
|
119
|
+
address mockOrderFiller = makeAddr("mockOrderFiller");
|
|
111
120
|
address[] memory trustedMessageSenders = new address[](0);
|
|
112
121
|
|
|
113
122
|
ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
|
|
@@ -120,7 +129,9 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
120
129
|
V4_POOL_MANAGER,
|
|
121
130
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
122
131
|
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
123
|
-
hookUpgradeGate
|
|
132
|
+
hookUpgradeGate,
|
|
133
|
+
mockOrderFiller,
|
|
134
|
+
mockHookRegistry
|
|
124
135
|
);
|
|
125
136
|
|
|
126
137
|
IHooks hook = HooksDeployment.deployHookWithSalt(
|
|
@@ -128,7 +139,9 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
128
139
|
V4_POOL_MANAGER,
|
|
129
140
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
130
141
|
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
131
|
-
hookUpgradeGate
|
|
142
|
+
hookUpgradeGate,
|
|
143
|
+
mockOrderFiller,
|
|
144
|
+
mockHookRegistry
|
|
132
145
|
),
|
|
133
146
|
salt
|
|
134
147
|
);
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {BaseTest} from "./utils/BaseTest.sol";
|
|
5
|
+
import {console} from "forge-std/console.sol";
|
|
6
|
+
|
|
7
|
+
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
8
|
+
import {IHasCreationInfo} from "../src/interfaces/IHasCreationInfo.sol";
|
|
9
|
+
import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
|
|
10
|
+
import {ContentCoin} from "../src/ContentCoin.sol";
|
|
11
|
+
import {ICoin} from "../src/interfaces/ICoin.sol";
|
|
12
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
13
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
14
|
+
import {MockERC20} from "./mocks/MockERC20.sol";
|
|
15
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
16
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
17
|
+
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
|
|
18
|
+
|
|
19
|
+
/// @notice Tests for the launch fee feature (time-based dynamic fee)
|
|
20
|
+
/// @dev IMPORTANT: This test uses forge-config pragma to run in isolation mode, which properly
|
|
21
|
+
/// simulates transaction boundaries for transient storage testing.
|
|
22
|
+
contract LaunchFeeTest is BaseTest {
|
|
23
|
+
MockERC20 internal mockCurrency;
|
|
24
|
+
ContentCoin internal coin;
|
|
25
|
+
|
|
26
|
+
function setUp() public override {
|
|
27
|
+
super.setUpNonForked();
|
|
28
|
+
|
|
29
|
+
mockCurrency = new MockERC20("MockCurrency", "MCK");
|
|
30
|
+
|
|
31
|
+
// Fund the pool manager with backing currency
|
|
32
|
+
mockCurrency.mint(address(poolManager), 1_000_000_000 ether);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// Interface Support Tests
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
function test_coinSupportsIHasCreationInfo() public {
|
|
40
|
+
_deployCoin();
|
|
41
|
+
|
|
42
|
+
bool supported = IERC165(address(coin)).supportsInterface(type(IHasCreationInfo).interfaceId);
|
|
43
|
+
assertTrue(supported, "coin should support IHasCreationInfo");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// forge-config: default.isolate = true
|
|
47
|
+
function test_creationInfo_returnsCorrectValues() public {
|
|
48
|
+
uint256 deployTime = block.timestamp;
|
|
49
|
+
_deployCoin();
|
|
50
|
+
|
|
51
|
+
(uint256 creationTimestamp, bool isDeploying) = IHasCreationInfo(address(coin)).creationInfo();
|
|
52
|
+
|
|
53
|
+
assertEq(creationTimestamp, deployTime, "creation timestamp should match deploy time");
|
|
54
|
+
assertFalse(isDeploying, "isDeploying should be false after deployment transaction");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ============================================
|
|
58
|
+
// Pool Key Tests
|
|
59
|
+
// ============================================
|
|
60
|
+
|
|
61
|
+
function test_poolKey_usesDynamicFeeFlag() public {
|
|
62
|
+
_deployCoin();
|
|
63
|
+
|
|
64
|
+
PoolKey memory poolKey = coin.getPoolKey();
|
|
65
|
+
|
|
66
|
+
assertEq(poolKey.fee, CoinConstants.DYNAMIC_FEE_FLAG, "pool fee should use DYNAMIC_FEE_FLAG");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================
|
|
70
|
+
// Launch Fee Calculation Tests
|
|
71
|
+
// ============================================
|
|
72
|
+
|
|
73
|
+
/// forge-config: default.isolate = true
|
|
74
|
+
function test_launchFee_immediatelyAfterCreation() public {
|
|
75
|
+
_deployCoin();
|
|
76
|
+
|
|
77
|
+
uint128 amountIn = 1 ether;
|
|
78
|
+
address trader = makeAddr("trader");
|
|
79
|
+
|
|
80
|
+
// Snapshot to compare swaps from same starting state
|
|
81
|
+
uint256 snapshot = vm.snapshotState();
|
|
82
|
+
|
|
83
|
+
// Swap immediately (same block, but different transaction)
|
|
84
|
+
// The launch fee should be at maximum (99%)
|
|
85
|
+
mockCurrency.mint(trader, amountIn);
|
|
86
|
+
uint256 coinBalanceBefore = coin.balanceOf(trader);
|
|
87
|
+
_swapCurrencyForCoin(amountIn, trader);
|
|
88
|
+
uint256 coinsAtMaxFee = coin.balanceOf(trader) - coinBalanceBefore;
|
|
89
|
+
|
|
90
|
+
console.log("Coins received at t=0 (99% fee):", coinsAtMaxFee);
|
|
91
|
+
|
|
92
|
+
// Revert to same starting state
|
|
93
|
+
vm.revertToState(snapshot);
|
|
94
|
+
|
|
95
|
+
// Warp past launch fee duration and do another swap from same starting state
|
|
96
|
+
vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 1);
|
|
97
|
+
|
|
98
|
+
mockCurrency.mint(trader, amountIn);
|
|
99
|
+
coinBalanceBefore = coin.balanceOf(trader);
|
|
100
|
+
_swapCurrencyForCoin(amountIn, trader);
|
|
101
|
+
uint256 coinsAtMinFee = coin.balanceOf(trader) - coinBalanceBefore;
|
|
102
|
+
|
|
103
|
+
console.log("Coins received at t>10s (1% fee):", coinsAtMinFee);
|
|
104
|
+
|
|
105
|
+
// Coins received with 1% fee should be significantly more than with 99% fee
|
|
106
|
+
assertGt(coinsAtMinFee, coinsAtMaxFee, "should receive more coins after launch fee ends");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// forge-config: default.isolate = true
|
|
110
|
+
function test_launchFee_decaysOverTime() public {
|
|
111
|
+
_deployCoin();
|
|
112
|
+
|
|
113
|
+
uint128 amountIn = 0.1 ether;
|
|
114
|
+
address trader = makeAddr("trader");
|
|
115
|
+
|
|
116
|
+
// Test at different time points
|
|
117
|
+
uint256[] memory timePoints = new uint256[](5);
|
|
118
|
+
timePoints[0] = 0; // 99% fee
|
|
119
|
+
timePoints[1] = 2; // ~79.2% fee
|
|
120
|
+
timePoints[2] = 5; // ~50% fee
|
|
121
|
+
timePoints[3] = 8; // ~20.8% fee
|
|
122
|
+
timePoints[4] = 10; // 1% fee
|
|
123
|
+
|
|
124
|
+
uint256[] memory coinsReceived = new uint256[](5);
|
|
125
|
+
|
|
126
|
+
for (uint256 i = 0; i < timePoints.length; i++) {
|
|
127
|
+
// Reset state for each test
|
|
128
|
+
uint256 snapshot = vm.snapshotState();
|
|
129
|
+
|
|
130
|
+
if (timePoints[i] > 0) {
|
|
131
|
+
vm.warp(block.timestamp + timePoints[i]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
mockCurrency.mint(trader, amountIn);
|
|
135
|
+
uint256 coinBalanceBefore = coin.balanceOf(trader);
|
|
136
|
+
|
|
137
|
+
_swapCurrencyForCoin(amountIn, trader);
|
|
138
|
+
|
|
139
|
+
coinsReceived[i] = coin.balanceOf(trader) - coinBalanceBefore;
|
|
140
|
+
|
|
141
|
+
console.log("Time:", timePoints[i], "s - Coins received:", coinsReceived[i]);
|
|
142
|
+
|
|
143
|
+
vm.revertToState(snapshot);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Verify monotonic increase (more coins as fee decreases)
|
|
147
|
+
for (uint256 i = 1; i < coinsReceived.length; i++) {
|
|
148
|
+
assertGt(coinsReceived[i], coinsReceived[i - 1], "coins received should increase as launch fee decays");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// forge-config: default.isolate = true
|
|
153
|
+
function test_launchFee_exactlyAtDuration() public {
|
|
154
|
+
_deployCoin();
|
|
155
|
+
|
|
156
|
+
uint128 amountIn = 0.1 ether;
|
|
157
|
+
address trader = makeAddr("trader");
|
|
158
|
+
|
|
159
|
+
// Test at exactly the launch fee duration
|
|
160
|
+
uint256 snapshot = vm.snapshotState();
|
|
161
|
+
vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION);
|
|
162
|
+
|
|
163
|
+
mockCurrency.mint(trader, amountIn);
|
|
164
|
+
uint256 coinBalanceBefore = coin.balanceOf(trader);
|
|
165
|
+
_swapCurrencyForCoin(amountIn, trader);
|
|
166
|
+
uint256 coinsAtExactDuration = coin.balanceOf(trader) - coinBalanceBefore;
|
|
167
|
+
|
|
168
|
+
vm.revertToState(snapshot);
|
|
169
|
+
|
|
170
|
+
// Test after the launch fee duration (same starting state)
|
|
171
|
+
vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 100);
|
|
172
|
+
|
|
173
|
+
mockCurrency.mint(trader, amountIn);
|
|
174
|
+
coinBalanceBefore = coin.balanceOf(trader);
|
|
175
|
+
_swapCurrencyForCoin(amountIn, trader);
|
|
176
|
+
uint256 coinsAfterDuration = coin.balanceOf(trader) - coinBalanceBefore;
|
|
177
|
+
|
|
178
|
+
// Should be approximately equal (both at 1% fee, same pool state)
|
|
179
|
+
assertApproxEqRel(coinsAtExactDuration, coinsAfterDuration, 0.01e18, "fee should be same at and after duration");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function test_launchFee_afterDurationEnds() public {
|
|
183
|
+
_deployCoin();
|
|
184
|
+
|
|
185
|
+
// Warp well past the launch fee duration
|
|
186
|
+
vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 1 days);
|
|
187
|
+
|
|
188
|
+
uint128 amountIn = 0.1 ether;
|
|
189
|
+
address trader = makeAddr("trader");
|
|
190
|
+
mockCurrency.mint(trader, amountIn);
|
|
191
|
+
|
|
192
|
+
// Should use normal 1% LP fee
|
|
193
|
+
_swapCurrencyForCoin(amountIn, trader);
|
|
194
|
+
|
|
195
|
+
// Just verify the swap succeeded - fee calculation is 1%
|
|
196
|
+
assertGt(coin.balanceOf(trader), 0, "trader should have received coins");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================
|
|
200
|
+
// Initial Supply Bypass Tests
|
|
201
|
+
// ============================================
|
|
202
|
+
|
|
203
|
+
function test_initialSupply_bypassesLaunchFee() public {
|
|
204
|
+
// The initial supply purchase during deployment should bypass launch fee
|
|
205
|
+
// This is verified by checking the creator receives coins during deployment
|
|
206
|
+
|
|
207
|
+
uint256 creatorBalanceBefore = 0; // Creator has no coins before deployment
|
|
208
|
+
|
|
209
|
+
_deployCoin();
|
|
210
|
+
|
|
211
|
+
uint256 creatorBalanceAfter = coin.balanceOf(users.creator);
|
|
212
|
+
|
|
213
|
+
// Creator should receive initial supply (10 million for content coins)
|
|
214
|
+
assertEq(creatorBalanceAfter, CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY, "creator should receive full initial supply without launch fee");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ============================================
|
|
218
|
+
// Fee Calculation Math Tests
|
|
219
|
+
// ============================================
|
|
220
|
+
|
|
221
|
+
function test_feeCalculation_linearDecay() public pure {
|
|
222
|
+
// Test the fee calculation formula
|
|
223
|
+
// fee = startFee - (elapsed / duration) * (startFee - endFee)
|
|
224
|
+
|
|
225
|
+
uint256 startFee = CoinConstants.LAUNCH_FEE_START; // 990,000 (99%)
|
|
226
|
+
uint256 endFee = CoinConstants.LP_FEE_V4; // 10,000 (1%)
|
|
227
|
+
uint256 duration = CoinConstants.LAUNCH_FEE_DURATION; // 10 seconds
|
|
228
|
+
|
|
229
|
+
// At t=0: fee should be 990,000
|
|
230
|
+
uint256 feeAt0 = startFee - (0 * (startFee - endFee)) / duration;
|
|
231
|
+
assertEq(feeAt0, 990_000, "fee at t=0");
|
|
232
|
+
|
|
233
|
+
// At t=5: fee should be 500,000 (50%)
|
|
234
|
+
uint256 feeAt5 = startFee - (5 * (startFee - endFee)) / duration;
|
|
235
|
+
assertEq(feeAt5, 500_000, "fee at t=5");
|
|
236
|
+
|
|
237
|
+
// At t=10: fee should be 10,000 (1%)
|
|
238
|
+
uint256 feeAt10 = startFee - (10 * (startFee - endFee)) / duration;
|
|
239
|
+
assertEq(feeAt10, 10_000, "fee at t=10");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ============================================
|
|
243
|
+
// Helper Functions
|
|
244
|
+
// ============================================
|
|
245
|
+
|
|
246
|
+
function _deployCoin() internal {
|
|
247
|
+
bytes32 salt = keccak256(abi.encodePacked("launchFeeTest", block.timestamp));
|
|
248
|
+
bytes memory poolConfig = _defaultPoolConfig(address(mockCurrency));
|
|
249
|
+
|
|
250
|
+
vm.prank(users.creator);
|
|
251
|
+
(address coinAddress, ) = factory.deploy(
|
|
252
|
+
users.creator,
|
|
253
|
+
_getDefaultOwners(),
|
|
254
|
+
"https://test.com",
|
|
255
|
+
"LaunchFeeCoin",
|
|
256
|
+
"LAUNCH",
|
|
257
|
+
poolConfig,
|
|
258
|
+
address(0), // no platform referrer
|
|
259
|
+
address(0), // no post deploy hook
|
|
260
|
+
bytes(""),
|
|
261
|
+
salt
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
coin = ContentCoin(payable(coinAddress));
|
|
265
|
+
vm.label(address(coin), "LAUNCH_FEE_COIN");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function _swapCurrencyForCoin(uint128 amountIn, address trader) internal {
|
|
269
|
+
uint128 minAmountOut = 0;
|
|
270
|
+
|
|
271
|
+
(bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
|
|
272
|
+
address(mockCurrency),
|
|
273
|
+
amountIn,
|
|
274
|
+
address(coin),
|
|
275
|
+
minAmountOut,
|
|
276
|
+
coin.getPoolKey(),
|
|
277
|
+
bytes("")
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
vm.startPrank(trader);
|
|
281
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(mockCurrency), amountIn, uint48(block.timestamp + 1 days));
|
|
282
|
+
|
|
283
|
+
router.execute(commands, inputs, block.timestamp + 1 days);
|
|
284
|
+
vm.stopPrank();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -26,6 +26,7 @@ import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
|
26
26
|
import {SwapParams} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
27
27
|
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
28
28
|
import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
29
|
+
import {IZoraLimitOrderBookCoinsInterface} from "../src/interfaces/IZoraLimitOrderBookCoinsInterface.sol";
|
|
29
30
|
|
|
30
31
|
contract LiquidityMigrationReceiver is IUpgradeableDestinationV4Hook, IERC165 {
|
|
31
32
|
function initializeFromMigration(
|
|
@@ -453,7 +454,14 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
453
454
|
|
|
454
455
|
// Now fix the bug by etching fixed hook code onto the old hook address
|
|
455
456
|
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
|
|
456
|
-
bytes memory creationCode = HooksDeployment.makeHookCreationCode(
|
|
457
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(
|
|
458
|
+
address(poolManager),
|
|
459
|
+
coinVersionLookup,
|
|
460
|
+
trustedMsgSenderLookup,
|
|
461
|
+
upgradeGate,
|
|
462
|
+
address(mockZoraLimitOrderBook),
|
|
463
|
+
makeAddr("mockHookRegistry")
|
|
464
|
+
);
|
|
457
465
|
|
|
458
466
|
(IHooks fixedHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
459
467
|
|
|
@@ -465,64 +473,64 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
465
473
|
coin.migrateLiquidity(newHook, "");
|
|
466
474
|
}
|
|
467
475
|
|
|
468
|
-
function test_migrateLiquidity_canUseNewFee() public {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
476
|
+
// function test_migrateLiquidity_canUseNewFee() public {
|
|
477
|
+
// // Reproduce the bug discovered in hook version 1.1.2 where migration
|
|
478
|
+
// // tries to modify liquidity positions that have zero liquidity
|
|
479
|
+
// vm.createSelectFork("base", 35754730);
|
|
472
480
|
|
|
473
|
-
|
|
474
|
-
|
|
481
|
+
// // jacob creator coin
|
|
482
|
+
// BaseCoin coin = BaseCoin(0x9B13358E3a023507E7046c18f508A958cDA75f54);
|
|
475
483
|
|
|
476
|
-
|
|
484
|
+
// address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2; // live upgrade gate
|
|
477
485
|
|
|
478
|
-
|
|
486
|
+
// uint24 oldFee = coin.getPoolKey().fee;
|
|
479
487
|
|
|
480
|
-
|
|
488
|
+
// assertEq(oldFee, 30000);
|
|
481
489
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
490
|
+
// // Now fix the bug by etching fixed hook code onto the old hook address
|
|
491
|
+
// ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
|
|
492
|
+
// bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup, upgradeGate, address(mockZoraLimitOrderBook));
|
|
485
493
|
|
|
486
|
-
|
|
494
|
+
// (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
487
495
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
496
|
+
// // Register upgrade path
|
|
497
|
+
// address[] memory baseImpls = new address[](1);
|
|
498
|
+
// baseImpls[0] = address(coin.hooks());
|
|
491
499
|
|
|
492
|
-
|
|
493
|
-
|
|
500
|
+
// vm.prank(Ownable(upgradeGate).owner());
|
|
501
|
+
// IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
|
|
494
502
|
|
|
495
|
-
|
|
496
|
-
|
|
503
|
+
// // Get coin owner
|
|
504
|
+
// address coinOwner = MultiOwnable(address(coin)).owners()[0];
|
|
497
505
|
|
|
498
|
-
|
|
499
|
-
|
|
506
|
+
// vm.prank(coinOwner);
|
|
507
|
+
// coin.migrateLiquidity(address(newHook), "");
|
|
500
508
|
|
|
501
|
-
|
|
502
|
-
|
|
509
|
+
// // fee should still be the same as before, because we didnt have the logic to update the fee in the old coin's hook.
|
|
510
|
+
// assertEq(coin.getPoolKey().fee, oldFee);
|
|
503
511
|
|
|
504
|
-
|
|
512
|
+
// address currencyAddress = address(coin.currency());
|
|
505
513
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
514
|
+
// // now test swapping the migrated liquidity
|
|
515
|
+
// address trader = makeAddr("trader");
|
|
516
|
+
// deal(currencyAddress, trader, 10 ether);
|
|
517
|
+
// _swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
|
|
510
518
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
519
|
+
// // now migrate liquidity again, but this time to the same new hook as before
|
|
520
|
+
// // since the bug has been fixed in the new hook, we should now be able to get the new fee
|
|
521
|
+
// // register the upgrade path for the new hook to itself
|
|
522
|
+
// baseImpls[0] = address(newHook);
|
|
523
|
+
// vm.prank(Ownable(upgradeGate).owner());
|
|
524
|
+
// IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
|
|
517
525
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
526
|
+
// // migrate liquidity again to the same new hook as before
|
|
527
|
+
// vm.prank(coinOwner);
|
|
528
|
+
// coin.migrateLiquidity(address(newHook), "");
|
|
521
529
|
|
|
522
|
-
|
|
523
|
-
|
|
530
|
+
// // the new fee should be the correct current fee
|
|
531
|
+
// assertEq(coin.getPoolKey().fee, CoinConstants.LP_FEE_V4);
|
|
524
532
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
533
|
+
// // now test swapping the migrated liquidity - it should work
|
|
534
|
+
// _swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
|
|
535
|
+
// }
|
|
528
536
|
}
|
package/test/MultiOwnable.t.sol
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.13;
|
|
3
3
|
|
|
4
|
-
import "./utils/BaseTest.sol";
|
|
4
|
+
import {BaseTest} from "./utils/BaseTest.sol";
|
|
5
|
+
import {MultiOwnable} from "../src/utils/MultiOwnable.sol";
|
|
5
6
|
|
|
6
7
|
contract MultiOwnableTest is BaseTest {
|
|
7
8
|
function setUp() public override {
|