@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.
@@ -269,13 +269,14 @@ library V4Liquidity {
269
269
  salt: 0
270
270
  });
271
271
 
272
- (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = poolManager.modifyLiquidity(poolKey, params, "");
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(liquidityDelta.amount0() + feesAccrued.amount0()),
278
- amount1Received: uint128(liquidityDelta.amount1() + feesAccrued.amount1())
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
- params = ModifyLiquidityParams({
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(positions[i].liquidity),
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.3.1";
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(V4_POOL_MANAGER, 0x777777751622c0d3258f214F9DF38E35BF45baF3, trustedMessageSenders, address(hookUpgradeGate))
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(V4_POOL_MANAGER, 0x777777751622c0d3258f214F9DF38E35BF45baF3, trustedMessageSenders, address(hookUpgradeGate))
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
- trustedMessageSenders,
88
+ ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
48
89
  hookUpgradeGate
49
90
  );
50
91
  IHooks hook = HooksDeployment.deployZoraV4CoinHook(
51
92
  V4_POOL_MANAGER,
52
93
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
53
- trustedMessageSenders,
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
- trustedMessageSenders,
122
+ ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
76
123
  hookUpgradeGate
77
124
  );
78
125
 
79
126
  IHooks hook = HooksDeployment.deployHookWithSalt(
80
- HooksDeployment.makeHookCreationCode(V4_POOL_MANAGER, 0x777777751622c0d3258f214F9DF38E35BF45baF3, trustedMessageSenders, hookUpgradeGate),
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
- bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
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
- bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
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
+ }
@@ -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
- bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
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
- bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
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
- // we added any extra liquidity to the last position, so we don't expect it to be the same
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
- // make sure that the new hook has no balance of 0 or 1
295
- assertApproxEqAbs(creatorCoin.getPoolKey().currency0.balanceOf(address(newHook)), 0, 10);
296
- assertApproxEqAbs(creatorCoin.getPoolKey().currency1.balanceOf(address(newHook)), 0, 10);
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
- bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
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
 
@@ -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
- deployCodeTo("FeeEstimatorHook.sol", abi.encode(address(poolManager), address(factory), hookUpgradeGate), hooks);
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(address[] memory trustedMessageSenders) public returns (bytes32 hookSalt) {
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), trustedMessageSenders, address(hookUpgradeGate));
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
- bytes32 hookSalt = getSalt(trustedMessageSenders);
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(V4_POOL_MANAGER, address(factory), trustedMessageSenders, address(hookUpgradeGate)),
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(address(mockAirlock));
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(address airlockAddress) internal {
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), trustedMessageSenders, address(hookUpgradeGate));
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
- trustedMessageSenders,
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, new address[](0), upgradeGate) {}
39
+ ) ZoraV4CoinHook(_poolManager, _coinVersionLookup, trustedMsgSenderLookup, upgradeGate) {}
38
40
 
39
41
  FeeEstimatorState public feeState;
40
42