@zoralabs/coins 2.3.1 → 2.4.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$colon$js.log +115 -116
- package/CHANGELOG.md +24 -0
- package/abis/BaseTest.json +3 -3
- package/abis/FeeEstimatorHook.json +23 -0
- package/abis/ITrustedMsgSenderProviderLookup.json +21 -0
- package/abis/IZoraV4CoinHook.json +5 -0
- package/abis/TrustedMsgSenderProviderLookup.json +215 -0
- package/abis/VmContractHelper242.json +233 -0
- package/abis/ZoraV4CoinHook.json +21 -3
- package/foundry.toml +5 -1
- package/package.json +3 -3
- package/script/DeployTrustedMsgSenderLookup.s.sol +20 -0
- package/src/deployment/CoinsDeployerBase.sol +22 -1
- package/src/hooks/ZoraV4CoinHook.sol +19 -55
- package/src/interfaces/ITrustedMsgSenderProviderLookup.sol +18 -0
- package/src/interfaces/IZoraV4CoinHook.sol +3 -0
- package/src/libs/HooksDeployment.sol +9 -8
- package/src/libs/V4Liquidity.sol +50 -6
- package/src/utils/TrustedMsgSenderProviderLookup.sol +73 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/CreatorCoinRewards.t.sol +4 -0
- package/test/HooksDeployment.t.sol +58 -6
- package/test/LiquidityMigration.t.sol +6 -2
- package/test/TrustedMsgSenderProviderLookup.t.sol +112 -0
- package/test/Upgrades.t.sol +15 -10
- package/test/utils/BaseTest.sol +35 -9
- package/test/utils/FeeEstimatorHook.sol +3 -1
- package/test/utils/TrustedSenderTestHelper.sol +18 -0
- /package/abis/{VmContractHelper239.json → VmContractHelper235.json} +0 -0
package/src/libs/V4Liquidity.sol
CHANGED
|
@@ -269,13 +269,14 @@ library V4Liquidity {
|
|
|
269
269
|
salt: 0
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
-
|
|
272
|
+
// callerDelta already includes fees, feesAccrued is informational only
|
|
273
|
+
(BalanceDelta callerDelta, ) = poolManager.modifyLiquidity(poolKey, params, "");
|
|
273
274
|
|
|
274
275
|
burnedPositions[i] = BurnedPosition({
|
|
275
276
|
tickLower: positions[i].tickLower,
|
|
276
277
|
tickUpper: positions[i].tickUpper,
|
|
277
|
-
amount0Received: uint128(
|
|
278
|
-
amount1Received: uint128(
|
|
278
|
+
amount0Received: uint128(callerDelta.amount0()),
|
|
279
|
+
amount1Received: uint128(callerDelta.amount1())
|
|
279
280
|
});
|
|
280
281
|
}
|
|
281
282
|
}
|
|
@@ -315,20 +316,63 @@ library V4Liquidity {
|
|
|
315
316
|
feeGrowthInside1DeltaX128 = feeGrowthInside1X128 - feeGrowthInside1LastX128;
|
|
316
317
|
}
|
|
317
318
|
|
|
319
|
+
/// @notice Mints liquidity positions into the pool
|
|
320
|
+
/// @dev Uses a defensive balance check to prevent ERC20InsufficientBalance errors during migration.
|
|
321
|
+
/// When burning positions from an old hook, the amounts received may not exactly match what's needed
|
|
322
|
+
/// to mint the same liquidity in the new hook due to:
|
|
323
|
+
/// 1. Rounding in getLiquidityForAmounts() when converting between liquidity and token amounts
|
|
324
|
+
/// 2. Price movements between burn and mint operations
|
|
325
|
+
/// 3. Any accumulated dust from previous operations
|
|
326
|
+
/// By capping each position's liquidity at what's actually mintable with remaining balances,
|
|
327
|
+
/// we ensure the migration never reverts due to insufficient tokens.
|
|
318
328
|
function mintPositions(IPoolManager poolManager, PoolKey memory poolKey, LpPosition[] memory positions) internal returns (int128 amount0, int128 amount1) {
|
|
319
|
-
ModifyLiquidityParams memory params;
|
|
320
329
|
uint256 numPositions = positions.length;
|
|
321
330
|
|
|
331
|
+
// Track remaining token balances throughout minting.
|
|
332
|
+
// These balances decrease as each position consumes tokens.
|
|
333
|
+
uint256 balance0 = poolKey.currency0.balanceOf(address(this));
|
|
334
|
+
uint256 balance1 = poolKey.currency1.balanceOf(address(this));
|
|
335
|
+
|
|
336
|
+
// Cache sqrt price once for all liquidity calculations
|
|
337
|
+
(uint160 sqrtPriceX96, , , ) = StateLibrary.getSlot0(poolManager, poolKey.toId());
|
|
338
|
+
|
|
322
339
|
for (uint256 i; i < numPositions; i++) {
|
|
323
|
-
|
|
340
|
+
if (positions[i].liquidity == 0) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Calculate the maximum liquidity we can mint given our remaining token balances.
|
|
345
|
+
// This is the key defensive check: even if the requested liquidity would require
|
|
346
|
+
// more tokens than we have (due to rounding), we cap it at what's actually possible.
|
|
347
|
+
uint128 maxLiquidity = LiquidityAmounts.getLiquidityForAmounts(
|
|
348
|
+
sqrtPriceX96,
|
|
349
|
+
TickMath.getSqrtPriceAtTick(positions[i].tickLower),
|
|
350
|
+
TickMath.getSqrtPriceAtTick(positions[i].tickUpper),
|
|
351
|
+
balance0,
|
|
352
|
+
balance1
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// Use the lesser of requested liquidity and what we can actually afford
|
|
356
|
+
uint128 liquidityToMint = positions[i].liquidity < maxLiquidity ? positions[i].liquidity : maxLiquidity;
|
|
357
|
+
|
|
358
|
+
if (liquidityToMint == 0) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
ModifyLiquidityParams memory params = ModifyLiquidityParams({
|
|
324
363
|
tickLower: positions[i].tickLower,
|
|
325
364
|
tickUpper: positions[i].tickUpper,
|
|
326
|
-
liquidityDelta: SafeCast.toInt256(
|
|
365
|
+
liquidityDelta: SafeCast.toInt256(liquidityToMint),
|
|
327
366
|
salt: 0
|
|
328
367
|
});
|
|
329
368
|
|
|
330
369
|
(BalanceDelta delta, ) = poolManager.modifyLiquidity(poolKey, params, "");
|
|
331
370
|
|
|
371
|
+
// Update remaining balances for next iteration.
|
|
372
|
+
// delta.amount0/1 are negative when minting (tokens flow out), so adding them decreases our balance.
|
|
373
|
+
balance0 = uint256(int256(balance0) + int256(delta.amount0()));
|
|
374
|
+
balance1 = uint256(int256(balance1) + int256(delta.amount1()));
|
|
375
|
+
|
|
332
376
|
amount0 += delta.amount0();
|
|
333
377
|
amount1 += delta.amount1();
|
|
334
378
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
|
|
2
|
+
// This software is licensed under the Zora Delayed Open Source License.
|
|
3
|
+
// Under this license, you may use, copy, modify, and distribute this software for
|
|
4
|
+
// non-commercial purposes only. Commercial use and competitive products are prohibited
|
|
5
|
+
// until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
|
|
6
|
+
// at which point this software automatically becomes available under the MIT License.
|
|
7
|
+
// Full license terms available at: https://docs.zora.co/coins/license
|
|
8
|
+
pragma solidity ^0.8.23;
|
|
9
|
+
|
|
10
|
+
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
|
|
11
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
12
|
+
import {ContractVersionBase} from "../version/ContractVersionBase.sol";
|
|
13
|
+
import {ITrustedMsgSenderProviderLookup} from "../interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
14
|
+
|
|
15
|
+
/// @title TrustedMsgSenderProviderLookup
|
|
16
|
+
/// @notice Contract for ITrustedMsgSenderProviderLookup that manages trusted message senders
|
|
17
|
+
/// @dev This contract allows the owner to add/remove trusted senders and provides lookup functionality
|
|
18
|
+
contract TrustedMsgSenderProviderLookup is ITrustedMsgSenderProviderLookup, ContractVersionBase, Ownable2Step {
|
|
19
|
+
/// @notice Emitted when a trusted sender is added
|
|
20
|
+
/// @param sender The address that was added as trusted
|
|
21
|
+
event TrustedSenderAdded(address indexed sender);
|
|
22
|
+
|
|
23
|
+
/// @notice Emitted when a trusted sender is removed
|
|
24
|
+
/// @param sender The address that was removed from trusted
|
|
25
|
+
event TrustedSenderRemoved(address indexed sender);
|
|
26
|
+
|
|
27
|
+
/// @notice Mapping of addresses to their trusted sender status
|
|
28
|
+
mapping(address => bool) private trustedSenders;
|
|
29
|
+
|
|
30
|
+
/// @notice Constructor that initializes the contract with trusted senders and sets the owner
|
|
31
|
+
/// @param trustedMessageSenders Array of addresses to mark as trusted senders initially
|
|
32
|
+
/// @param initialOwner The address that will own this contract
|
|
33
|
+
constructor(address[] memory trustedMessageSenders, address initialOwner) Ownable(initialOwner) {
|
|
34
|
+
for (uint256 i = 0; i < trustedMessageSenders.length; i++) {
|
|
35
|
+
trustedSenders[trustedMessageSenders[i]] = true;
|
|
36
|
+
emit TrustedSenderAdded(trustedMessageSenders[i]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// @notice Checks if an address is a trusted message sender provider
|
|
41
|
+
/// @param sender The address to check
|
|
42
|
+
/// @return true if the sender is trusted, false otherwise
|
|
43
|
+
function isTrustedMsgSenderProvider(address sender) external view override returns (bool) {
|
|
44
|
+
return trustedSenders[sender];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// @notice Adds multiple trusted senders in a single transaction (only callable by owner)
|
|
48
|
+
/// @param senders Array of addresses to add as trusted
|
|
49
|
+
function addTrustedMsgSenderProviders(address[] calldata senders) external onlyOwner {
|
|
50
|
+
for (uint256 i = 0; i < senders.length; i++) {
|
|
51
|
+
address sender = senders[i];
|
|
52
|
+
require(sender != address(0), "Cannot add zero address as trusted sender");
|
|
53
|
+
|
|
54
|
+
if (!trustedSenders[sender]) {
|
|
55
|
+
trustedSenders[sender] = true;
|
|
56
|
+
emit TrustedSenderAdded(sender);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// @notice Removes multiple trusted senders in a single transaction (only callable by owner)
|
|
62
|
+
/// @param senders Array of addresses to remove from trusted
|
|
63
|
+
function removeTrustedMsgSenderProviders(address[] calldata senders) external onlyOwner {
|
|
64
|
+
for (uint256 i = 0; i < senders.length; i++) {
|
|
65
|
+
address sender = senders[i];
|
|
66
|
+
|
|
67
|
+
if (trustedSenders[sender]) {
|
|
68
|
+
trustedSenders[sender] = false;
|
|
69
|
+
emit TrustedSenderRemoved(sender);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -9,6 +9,6 @@ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersion
|
|
|
9
9
|
contract ContractVersionBase is IVersionedContract {
|
|
10
10
|
/// @notice The version of the contract
|
|
11
11
|
function contractVersion() external pure override returns (string memory) {
|
|
12
|
-
return "2.
|
|
12
|
+
return "2.4.0";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -289,12 +289,16 @@ contract CreatorCoinRewardsTest is BaseTest {
|
|
|
289
289
|
function test_buy_then_sell_both_referrers() public {
|
|
290
290
|
uint128 buyAmount = 100 ether; // Fixed amount
|
|
291
291
|
|
|
292
|
+
console.log("deploying creator coin with platform referrer");
|
|
292
293
|
// Deploy CreatorCoin with platform referrer
|
|
293
294
|
_deployCreatorCoin(true);
|
|
294
295
|
|
|
296
|
+
console.log("buying creator coin with both referrers");
|
|
295
297
|
// Step 1: Buy creator coin (ZORA -> Creator Coin)
|
|
296
298
|
_buyCreatorCoin(buyAmount, true);
|
|
297
299
|
|
|
300
|
+
console.log("buyer's creator coin balance", creatorCoin.balanceOf(users.buyer));
|
|
301
|
+
|
|
298
302
|
// Get buyer's creator coin balance after purchase
|
|
299
303
|
uint256 creatorCoinBalance = creatorCoin.balanceOf(users.buyer);
|
|
300
304
|
require(creatorCoinBalance > 0, "Buyer must have creator coin balance to sell");
|
|
@@ -8,27 +8,62 @@ import {ContractAddresses} from "./utils/ContractAddresses.sol";
|
|
|
8
8
|
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
|
|
9
9
|
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
|
|
10
10
|
import {HookUpgradeGate} from "../src/hooks/HookUpgradeGate.sol";
|
|
11
|
+
import {ITrustedMsgSenderProviderLookup} from "../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
12
|
+
import {TrustedSenderTestHelper} from "./utils/TrustedSenderTestHelper.sol";
|
|
11
13
|
|
|
12
14
|
contract HooksDeploymentTest is Test, ContractAddresses {
|
|
13
15
|
address internal hookUpgradeGate;
|
|
16
|
+
ITrustedMsgSenderProviderLookup internal trustedMsgSenderLookup;
|
|
17
|
+
address internal owner;
|
|
18
|
+
address internal nonOwner;
|
|
19
|
+
address internal trustedSender1;
|
|
20
|
+
address internal trustedSender2;
|
|
21
|
+
address internal nonTrustedSender;
|
|
14
22
|
|
|
15
23
|
function setUp() public {
|
|
16
24
|
vm.createSelectFork("base", 31653138);
|
|
17
25
|
|
|
26
|
+
owner = makeAddr("owner");
|
|
27
|
+
nonOwner = makeAddr("nonOwner");
|
|
28
|
+
trustedSender1 = makeAddr("trustedSender1");
|
|
29
|
+
trustedSender2 = makeAddr("trustedSender2");
|
|
30
|
+
nonTrustedSender = makeAddr("nonTrustedSender");
|
|
31
|
+
|
|
18
32
|
hookUpgradeGate = address(new HookUpgradeGate(makeAddr("factoryOwner")));
|
|
33
|
+
|
|
34
|
+
// Initialize with one trusted sender
|
|
35
|
+
address[] memory initialTrustedSenders = new address[](1);
|
|
36
|
+
initialTrustedSenders[0] = trustedSender1;
|
|
37
|
+
|
|
38
|
+
trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(owner, initialTrustedSenders);
|
|
19
39
|
}
|
|
20
40
|
|
|
21
41
|
function test_canMineAndCacheSalt() public {
|
|
22
42
|
address[] memory trustedMessageSenders = new address[](0);
|
|
23
43
|
|
|
44
|
+
ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
|
|
45
|
+
makeAddr("owner"),
|
|
46
|
+
trustedMessageSenders
|
|
47
|
+
);
|
|
48
|
+
|
|
24
49
|
(bytes32 salt, ) = HooksDeployment.mineAndCacheSalt(
|
|
25
50
|
address(this),
|
|
26
|
-
abi.encode(
|
|
51
|
+
abi.encode(
|
|
52
|
+
V4_POOL_MANAGER,
|
|
53
|
+
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
54
|
+
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
55
|
+
address(hookUpgradeGate)
|
|
56
|
+
)
|
|
27
57
|
);
|
|
28
58
|
|
|
29
59
|
(bytes32 salt2, bool wasCached2) = HooksDeployment.mineAndCacheSalt(
|
|
30
60
|
address(this),
|
|
31
|
-
abi.encode(
|
|
61
|
+
abi.encode(
|
|
62
|
+
V4_POOL_MANAGER,
|
|
63
|
+
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
64
|
+
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
65
|
+
address(hookUpgradeGate)
|
|
66
|
+
)
|
|
32
67
|
);
|
|
33
68
|
|
|
34
69
|
assertEq(salt, salt2);
|
|
@@ -40,17 +75,23 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
40
75
|
vm.createSelectFork("base", 31653138);
|
|
41
76
|
|
|
42
77
|
address[] memory trustedMessageSenders = new address[](0);
|
|
78
|
+
|
|
79
|
+
ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
|
|
80
|
+
makeAddr("owner"),
|
|
81
|
+
trustedMessageSenders
|
|
82
|
+
);
|
|
83
|
+
|
|
43
84
|
(, bytes32 salt) = HooksDeployment.mineForCoinSalt(
|
|
44
85
|
address(this),
|
|
45
86
|
V4_POOL_MANAGER,
|
|
46
87
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
47
|
-
|
|
88
|
+
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
48
89
|
hookUpgradeGate
|
|
49
90
|
);
|
|
50
91
|
IHooks hook = HooksDeployment.deployZoraV4CoinHook(
|
|
51
92
|
V4_POOL_MANAGER,
|
|
52
93
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
53
|
-
|
|
94
|
+
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
54
95
|
hookUpgradeGate,
|
|
55
96
|
salt
|
|
56
97
|
);
|
|
@@ -68,16 +109,27 @@ contract HooksDeploymentTest is Test, ContractAddresses {
|
|
|
68
109
|
vm.createSelectFork("base", 31653138);
|
|
69
110
|
|
|
70
111
|
address[] memory trustedMessageSenders = new address[](0);
|
|
112
|
+
|
|
113
|
+
ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
|
|
114
|
+
makeAddr("owner"),
|
|
115
|
+
trustedMessageSenders
|
|
116
|
+
);
|
|
117
|
+
|
|
71
118
|
(, bytes32 salt) = HooksDeployment.mineForCoinSalt(
|
|
72
119
|
address(this),
|
|
73
120
|
V4_POOL_MANAGER,
|
|
74
121
|
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
75
|
-
|
|
122
|
+
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
76
123
|
hookUpgradeGate
|
|
77
124
|
);
|
|
78
125
|
|
|
79
126
|
IHooks hook = HooksDeployment.deployHookWithSalt(
|
|
80
|
-
HooksDeployment.makeHookCreationCode(
|
|
127
|
+
HooksDeployment.makeHookCreationCode(
|
|
128
|
+
V4_POOL_MANAGER,
|
|
129
|
+
0x777777751622c0d3258f214F9DF38E35BF45baF3,
|
|
130
|
+
ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
|
|
131
|
+
hookUpgradeGate
|
|
132
|
+
),
|
|
81
133
|
salt
|
|
82
134
|
);
|
|
83
135
|
|
|
@@ -4,6 +4,8 @@ pragma solidity ^0.8.23;
|
|
|
4
4
|
import {MockERC20} from "./mocks/MockERC20.sol";
|
|
5
5
|
import {BaseTest} from "./utils/BaseTest.sol";
|
|
6
6
|
import {HooksDeployment} from "../src/libs/HooksDeployment.sol";
|
|
7
|
+
import {TrustedSenderTestHelper} from "./utils/TrustedSenderTestHelper.sol";
|
|
8
|
+
import {ITrustedMsgSenderProviderLookup} from "../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
7
9
|
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
|
|
8
10
|
import {IUpgradeableV4Hook, IUpgradeableDestinationV4Hook, BurnedPosition} from "../src/interfaces/IUpgradeableV4Hook.sol";
|
|
9
11
|
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
@@ -450,7 +452,8 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
450
452
|
coin.migrateLiquidity(newHook, "");
|
|
451
453
|
|
|
452
454
|
// Now fix the bug by etching fixed hook code onto the old hook address
|
|
453
|
-
|
|
455
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
|
|
456
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup, upgradeGate);
|
|
454
457
|
|
|
455
458
|
(IHooks fixedHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
456
459
|
|
|
@@ -477,7 +480,8 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
477
480
|
assertEq(oldFee, 30000);
|
|
478
481
|
|
|
479
482
|
// Now fix the bug by etching fixed hook code onto the old hook address
|
|
480
|
-
|
|
483
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
|
|
484
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup, upgradeGate);
|
|
481
485
|
|
|
482
486
|
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
483
487
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import {TrustedMsgSenderProviderLookup} from "../src/utils/TrustedMsgSenderProviderLookup.sol";
|
|
6
|
+
import {ITrustedMsgSenderProviderLookup} from "../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
7
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
8
|
+
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
|
|
9
|
+
|
|
10
|
+
contract TrustedMsgSenderProviderLookupTest is Test {
|
|
11
|
+
ITrustedMsgSenderProviderLookup internal trustedMsgSenderLookup;
|
|
12
|
+
|
|
13
|
+
address internal owner;
|
|
14
|
+
address internal nonOwner;
|
|
15
|
+
address internal trustedSender1;
|
|
16
|
+
address internal trustedSender2;
|
|
17
|
+
|
|
18
|
+
event TrustedSenderAdded(address indexed sender);
|
|
19
|
+
event TrustedSenderRemoved(address indexed sender);
|
|
20
|
+
|
|
21
|
+
function setUp() public {
|
|
22
|
+
owner = makeAddr("owner");
|
|
23
|
+
nonOwner = makeAddr("nonOwner");
|
|
24
|
+
trustedSender1 = makeAddr("trustedSender1");
|
|
25
|
+
trustedSender2 = makeAddr("trustedSender2");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function deployAndInitializeLookup(address[] memory initialTrustedSenders, address initialOwner) internal returns (ITrustedMsgSenderProviderLookup) {
|
|
29
|
+
// Deploy the contract directly using constructor
|
|
30
|
+
TrustedMsgSenderProviderLookup lookup = new TrustedMsgSenderProviderLookup(initialTrustedSenders, initialOwner);
|
|
31
|
+
return ITrustedMsgSenderProviderLookup(address(lookup));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function test_constructor_initializesCorrectly() public {
|
|
35
|
+
address[] memory initialTrustedSenders = new address[](2);
|
|
36
|
+
initialTrustedSenders[0] = trustedSender1;
|
|
37
|
+
initialTrustedSenders[1] = trustedSender2;
|
|
38
|
+
|
|
39
|
+
trustedMsgSenderLookup = deployAndInitializeLookup(initialTrustedSenders, owner);
|
|
40
|
+
|
|
41
|
+
assertEq(Ownable2Step(address(trustedMsgSenderLookup)).owner(), owner);
|
|
42
|
+
assertTrue(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender1));
|
|
43
|
+
assertTrue(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender2));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function test_isTrustedMsgSenderProvider_returnsCorrectValues() public {
|
|
47
|
+
address[] memory initialTrustedSenders = new address[](1);
|
|
48
|
+
initialTrustedSenders[0] = trustedSender1;
|
|
49
|
+
|
|
50
|
+
trustedMsgSenderLookup = deployAndInitializeLookup(initialTrustedSenders, owner);
|
|
51
|
+
|
|
52
|
+
assertTrue(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender1));
|
|
53
|
+
assertFalse(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender2));
|
|
54
|
+
assertFalse(trustedMsgSenderLookup.isTrustedMsgSenderProvider(address(0)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function test_addTrustedMsgSenderProviders_worksCorrectly() public {
|
|
58
|
+
address[] memory emptyTrustedSenders = new address[](0);
|
|
59
|
+
trustedMsgSenderLookup = deployAndInitializeLookup(emptyTrustedSenders, owner);
|
|
60
|
+
|
|
61
|
+
address[] memory sendersToAdd = new address[](2);
|
|
62
|
+
sendersToAdd[0] = trustedSender1;
|
|
63
|
+
sendersToAdd[1] = trustedSender2;
|
|
64
|
+
|
|
65
|
+
vm.prank(owner);
|
|
66
|
+
TrustedMsgSenderProviderLookup(address(trustedMsgSenderLookup)).addTrustedMsgSenderProviders(sendersToAdd);
|
|
67
|
+
|
|
68
|
+
assertTrue(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender1));
|
|
69
|
+
assertTrue(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender2));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function test_addTrustedMsgSenderProviders_onlyOwnerCanAdd() public {
|
|
73
|
+
address[] memory emptyTrustedSenders = new address[](0);
|
|
74
|
+
trustedMsgSenderLookup = deployAndInitializeLookup(emptyTrustedSenders, owner);
|
|
75
|
+
|
|
76
|
+
address[] memory sendersToAdd = new address[](1);
|
|
77
|
+
sendersToAdd[0] = trustedSender1;
|
|
78
|
+
|
|
79
|
+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, nonOwner));
|
|
80
|
+
vm.prank(nonOwner);
|
|
81
|
+
TrustedMsgSenderProviderLookup(address(trustedMsgSenderLookup)).addTrustedMsgSenderProviders(sendersToAdd);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function test_removeTrustedMsgSenderProviders_worksCorrectly() public {
|
|
85
|
+
address[] memory initialTrustedSenders = new address[](2);
|
|
86
|
+
initialTrustedSenders[0] = trustedSender1;
|
|
87
|
+
initialTrustedSenders[1] = trustedSender2;
|
|
88
|
+
trustedMsgSenderLookup = deployAndInitializeLookup(initialTrustedSenders, owner);
|
|
89
|
+
|
|
90
|
+
address[] memory sendersToRemove = new address[](1);
|
|
91
|
+
sendersToRemove[0] = trustedSender1;
|
|
92
|
+
|
|
93
|
+
vm.prank(owner);
|
|
94
|
+
TrustedMsgSenderProviderLookup(address(trustedMsgSenderLookup)).removeTrustedMsgSenderProviders(sendersToRemove);
|
|
95
|
+
|
|
96
|
+
assertFalse(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender1));
|
|
97
|
+
assertTrue(trustedMsgSenderLookup.isTrustedMsgSenderProvider(trustedSender2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function test_removeTrustedMsgSenderProviders_onlyOwnerCanRemove() public {
|
|
101
|
+
address[] memory initialTrustedSenders = new address[](1);
|
|
102
|
+
initialTrustedSenders[0] = trustedSender1;
|
|
103
|
+
trustedMsgSenderLookup = deployAndInitializeLookup(initialTrustedSenders, owner);
|
|
104
|
+
|
|
105
|
+
address[] memory sendersToRemove = new address[](1);
|
|
106
|
+
sendersToRemove[0] = trustedSender1;
|
|
107
|
+
|
|
108
|
+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, nonOwner));
|
|
109
|
+
vm.prank(nonOwner);
|
|
110
|
+
TrustedMsgSenderProviderLookup(address(trustedMsgSenderLookup)).removeTrustedMsgSenderProviders(sendersToRemove);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/test/Upgrades.t.sol
CHANGED
|
@@ -28,6 +28,8 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
|
|
|
28
28
|
import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
|
|
29
29
|
import {PoolStateReader} from "../src/libs/PoolStateReader.sol";
|
|
30
30
|
import {LpPosition} from "../src/types/LpPosition.sol";
|
|
31
|
+
import {ITrustedMsgSenderProviderLookup} from "../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
32
|
+
import {TrustedSenderTestHelper} from "./utils/TrustedSenderTestHelper.sol";
|
|
31
33
|
|
|
32
34
|
contract BadImpl {
|
|
33
35
|
function contractName() public pure returns (string memory) {
|
|
@@ -195,7 +197,9 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
195
197
|
|
|
196
198
|
uint256 amountIn = 0.000111 ether;
|
|
197
199
|
|
|
198
|
-
|
|
200
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup2 = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
|
|
201
|
+
|
|
202
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup2, upgradeGate);
|
|
199
203
|
|
|
200
204
|
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
201
205
|
|
|
@@ -247,7 +251,9 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
247
251
|
|
|
248
252
|
address existingHook = address(creatorCoin.hooks());
|
|
249
253
|
|
|
250
|
-
|
|
254
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup3 = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
|
|
255
|
+
|
|
256
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup3, upgradeGate);
|
|
251
257
|
|
|
252
258
|
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
253
259
|
|
|
@@ -281,19 +287,17 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
281
287
|
uint128[] memory afterLiquidity = getLiquidityForPositions(afterKey, afterPositions);
|
|
282
288
|
|
|
283
289
|
for (uint256 i = 0; i < beforeLiquidity.length; i++) {
|
|
284
|
-
|
|
285
|
-
if (i != beforeLiquidity.length - 1) {
|
|
286
|
-
assertApproxEqAbs(beforeLiquidity[i], afterLiquidity[i], 200);
|
|
287
|
-
}
|
|
290
|
+
assertApproxEqAbs(beforeLiquidity[i], afterLiquidity[i], 200);
|
|
288
291
|
}
|
|
289
292
|
|
|
290
293
|
uint160 afterPrice = PoolStateReader.getSqrtPriceX96(creatorCoin.getPoolKey(), poolManager);
|
|
291
294
|
|
|
292
295
|
assertEq(beforePrice, afterPrice);
|
|
293
296
|
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
assertApproxEqAbs(creatorCoin.getPoolKey().
|
|
297
|
+
// Small amounts of dust from rounding may remain in the hook as burned tokens
|
|
298
|
+
// This is expected and saves on code size by not minting them back into the pool
|
|
299
|
+
assertApproxEqAbs(creatorCoin.getPoolKey().currency0.balanceOf(address(newHook)), 0, 0.1 ether);
|
|
300
|
+
assertApproxEqAbs(creatorCoin.getPoolKey().currency1.balanceOf(address(newHook)), 0, 0.1 ether);
|
|
297
301
|
|
|
298
302
|
// now try to swap some currency for the creator coin - it should succeed
|
|
299
303
|
_swapSomeCurrencyForCoin(creatorCoin, zora, uint128(IERC20(zora).balanceOf(trader) / 2), trader);
|
|
@@ -315,7 +319,8 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
315
319
|
// this swap should revert because the content coin is broken
|
|
316
320
|
_swapSomeCurrencyForCoinAndExpectRevert(ICoin(contentCoin), creatorCoin, uint128(amountIn), trader);
|
|
317
321
|
|
|
318
|
-
|
|
322
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup2 = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
|
|
323
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup2, upgradeGate);
|
|
319
324
|
|
|
320
325
|
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
321
326
|
|
package/test/utils/BaseTest.sol
CHANGED
|
@@ -44,6 +44,9 @@ import {ContractAddresses} from "./ContractAddresses.sol";
|
|
|
44
44
|
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
|
|
45
45
|
import {HookUpgradeGate} from "../../src/hooks/HookUpgradeGate.sol";
|
|
46
46
|
import {ZoraHookRegistry} from "../../src/hook-registry/ZoraHookRegistry.sol";
|
|
47
|
+
import {TrustedMsgSenderProviderLookup} from "../../src/utils/TrustedMsgSenderProviderLookup.sol";
|
|
48
|
+
import {ITrustedMsgSenderProviderLookup} from "../../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
49
|
+
import {TrustedSenderTestHelper} from "./TrustedSenderTestHelper.sol";
|
|
47
50
|
|
|
48
51
|
// Hookmate imports for non-forked testing
|
|
49
52
|
import {V4PoolManagerDeployer} from "./hookmate/artifacts/V4PoolManager.sol";
|
|
@@ -215,13 +218,23 @@ contract BaseTest is Test, ContractAddresses {
|
|
|
215
218
|
}
|
|
216
219
|
|
|
217
220
|
function _deployFeeEstimatorHook(address hooks) internal {
|
|
218
|
-
|
|
221
|
+
// Deploy a new lookup with the same trusted senders
|
|
222
|
+
address[] memory trustedMessageSenders = new address[](2);
|
|
223
|
+
trustedMessageSenders[0] = UNIVERSAL_ROUTER;
|
|
224
|
+
trustedMessageSenders[1] = V4_POSITION_MANAGER;
|
|
225
|
+
ITrustedMsgSenderProviderLookup newLookup = TrustedSenderTestHelper.deployTrustedMessageSender(users.factoryOwner, trustedMessageSenders);
|
|
226
|
+
|
|
227
|
+
deployCodeTo(
|
|
228
|
+
"FeeEstimatorHook.sol",
|
|
229
|
+
HooksDeployment.hookConstructorArgs(address(poolManager), address(factory), newLookup, address(hookUpgradeGate)),
|
|
230
|
+
hooks
|
|
231
|
+
);
|
|
219
232
|
}
|
|
220
233
|
|
|
221
|
-
function getSalt(
|
|
234
|
+
function getSalt(ITrustedMsgSenderProviderLookup trustedMsgSenderLookup) public returns (bytes32 hookSalt) {
|
|
222
235
|
address deployer = address(this);
|
|
223
236
|
|
|
224
|
-
(, hookSalt) = HooksDeployment.mineForCoinSalt(deployer, V4_POOL_MANAGER, address(factory),
|
|
237
|
+
(, hookSalt) = HooksDeployment.mineForCoinSalt(deployer, V4_POOL_MANAGER, address(factory), trustedMsgSenderLookup, address(hookUpgradeGate));
|
|
225
238
|
}
|
|
226
239
|
|
|
227
240
|
function _deployHooks() internal {
|
|
@@ -229,13 +242,20 @@ contract BaseTest is Test, ContractAddresses {
|
|
|
229
242
|
trustedMessageSenders[0] = UNIVERSAL_ROUTER;
|
|
230
243
|
trustedMessageSenders[1] = V4_POSITION_MANAGER;
|
|
231
244
|
|
|
232
|
-
|
|
245
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(users.factoryOwner, trustedMessageSenders);
|
|
246
|
+
|
|
247
|
+
bytes32 hookSalt = getSalt(trustedMsgSenderLookup);
|
|
233
248
|
|
|
234
249
|
hook = ZoraV4CoinHook(
|
|
235
250
|
payable(
|
|
236
251
|
address(
|
|
237
252
|
HooksDeployment.deployHookWithSalt(
|
|
238
|
-
HooksDeployment.makeHookCreationCode(
|
|
253
|
+
HooksDeployment.makeHookCreationCode(
|
|
254
|
+
V4_POOL_MANAGER,
|
|
255
|
+
address(factory),
|
|
256
|
+
ITrustedMsgSenderProviderLookup(address(trustedMsgSenderLookup)),
|
|
257
|
+
address(hookUpgradeGate)
|
|
258
|
+
),
|
|
239
259
|
hookSalt
|
|
240
260
|
)
|
|
241
261
|
)
|
|
@@ -360,7 +380,7 @@ contract BaseTest is Test, ContractAddresses {
|
|
|
360
380
|
zoraHookRegistry.initialize(initialOwners);
|
|
361
381
|
|
|
362
382
|
// Deploy hooks for non-forked environment
|
|
363
|
-
_deployHooksNonForked(
|
|
383
|
+
_deployHooksNonForked();
|
|
364
384
|
|
|
365
385
|
// Deploy coin implementations
|
|
366
386
|
coinV4Impl = new ContentCoin(users.feeRecipient, address(protocolRewards), poolManager, address(mockAirlock));
|
|
@@ -406,6 +426,7 @@ contract BaseTest is Test, ContractAddresses {
|
|
|
406
426
|
}
|
|
407
427
|
|
|
408
428
|
permit2 = IPermit2(permit2Address);
|
|
429
|
+
vm.label(address(permit2), "V4_PERMIT2");
|
|
409
430
|
}
|
|
410
431
|
|
|
411
432
|
function _deployPoolManagerNonForked() internal {
|
|
@@ -416,10 +437,12 @@ contract BaseTest is Test, ContractAddresses {
|
|
|
416
437
|
}
|
|
417
438
|
|
|
418
439
|
deal(address(poolManager), 10000 ether);
|
|
440
|
+
vm.label(address(poolManager), "V4_POOL_MANAGER");
|
|
419
441
|
}
|
|
420
442
|
|
|
421
443
|
function _deployQuoterNonForked() internal {
|
|
422
444
|
quoter = IV4Quoter(V4QuoterDeployer.deploy(address(poolManager)));
|
|
445
|
+
vm.label(address(quoter), "V4_QUOTER");
|
|
423
446
|
}
|
|
424
447
|
|
|
425
448
|
function _deployUniversalRouterNonForked() internal {
|
|
@@ -435,20 +458,23 @@ contract BaseTest is Test, ContractAddresses {
|
|
|
435
458
|
v4PositionManager: address(0)
|
|
436
459
|
});
|
|
437
460
|
router = IUniversalRouter(UniversalRouterDeployer.deploy(params));
|
|
461
|
+
vm.label(address(router), "UNIVERSAL_ROUTER");
|
|
438
462
|
}
|
|
439
463
|
|
|
440
|
-
function _deployHooksNonForked(
|
|
464
|
+
function _deployHooksNonForked() internal {
|
|
441
465
|
address[] memory trustedMessageSenders = new address[](1);
|
|
442
466
|
trustedMessageSenders[0] = address(router);
|
|
443
467
|
|
|
468
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(users.factoryOwner, trustedMessageSenders);
|
|
469
|
+
|
|
444
470
|
// Use proper salt mining for hook deployment
|
|
445
471
|
address deployer = address(this);
|
|
446
|
-
(, bytes32 salt) = HooksDeployment.mineForCoinSalt(deployer, address(poolManager), address(factory),
|
|
472
|
+
(, bytes32 salt) = HooksDeployment.mineForCoinSalt(deployer, address(poolManager), address(factory), trustedMsgSenderLookup, address(hookUpgradeGate));
|
|
447
473
|
|
|
448
474
|
bytes memory hookCreationCode = HooksDeployment.makeHookCreationCode(
|
|
449
475
|
address(poolManager),
|
|
450
476
|
address(factory),
|
|
451
|
-
|
|
477
|
+
trustedMsgSenderLookup,
|
|
452
478
|
address(hookUpgradeGate)
|
|
453
479
|
);
|
|
454
480
|
|
|
@@ -16,6 +16,7 @@ import {UniV4SwapToCurrency} from "../../src/libs/UniV4SwapToCurrency.sol";
|
|
|
16
16
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
17
17
|
import {CoinRewardsV4} from "../../src/libs/CoinRewardsV4.sol";
|
|
18
18
|
import {IHooksUpgradeGate} from "../../src/interfaces/IHooksUpgradeGate.sol";
|
|
19
|
+
import {ITrustedMsgSenderProviderLookup} from "../../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
19
20
|
|
|
20
21
|
/// @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
|
|
21
22
|
contract FeeEstimatorHook is ZoraV4CoinHook {
|
|
@@ -33,8 +34,9 @@ contract FeeEstimatorHook is ZoraV4CoinHook {
|
|
|
33
34
|
constructor(
|
|
34
35
|
IPoolManager _poolManager,
|
|
35
36
|
IDeployedCoinVersionLookup _coinVersionLookup,
|
|
37
|
+
ITrustedMsgSenderProviderLookup trustedMsgSenderLookup,
|
|
36
38
|
IHooksUpgradeGate upgradeGate
|
|
37
|
-
) ZoraV4CoinHook(_poolManager, _coinVersionLookup,
|
|
39
|
+
) ZoraV4CoinHook(_poolManager, _coinVersionLookup, trustedMsgSenderLookup, upgradeGate) {}
|
|
38
40
|
|
|
39
41
|
FeeEstimatorState public feeState;
|
|
40
42
|
|