From 0a97fda32ae85d122df4fe9c91a3363e7b1a5800 Mon Sep 17 00:00:00 2001
From: marktoda <toda.mark@gmail.com>
Date: Fri, 8 Nov 2024 05:50:22 -0800
Subject: [PATCH 1/2] feat: add UniswapV4DeployerCompetition (#117)

* feat: add UniswapV4DeployerCompetition

this commit adds a contract that creates a competition to generate the
vanity address for Uniswap V4. It does so using CREATE2 salts,
pre-calculating the address at which the contract will be deployed and
applying a score to the address based on its vanity. leading 0's are
weighted most heavily, followed by other 0's and 4's. The winner
receives an NFT, a bounty, and deployer privileges

* add comments

* change scoring to discuss

* interface plus tests

* inheritdoc tags

* change scoring algorithm

* feat: remove prizes

this commit removes the ETH payout, NFT mint, and exclusive deploy
rights

* feat: add natspec

* fix: snaps

* feat: improve test

* feat: cleanup scoring code

* fix: typo

* update constructor parameters for pool manager

* correct snapshots

* Include address in salt

* remove console logs

* correct test name

* More constructor parameters

* fix: remove override keywords

* fix: remove unused code

* fix: use default create2 function signature

* feat: remove unused import

* fix: remove unused v4Owner variable

* fix: add rules to natspec

* fix(deployComp): remove bestaddress storage

This commit removes the storage variable for bestAddres, calculating it
on-the-fly instead. It also just takes the initial `salt = 0` value as
a default score-to-beat rather than a sentinel for any salt is allowed
as a simplification

* fix: minor nits

- comparison strictness match comments
- unchecked vanity math

* snapshot

---------

Co-authored-by: dianakocsis <diana.kocsis@uniswap.org>
Co-authored-by: hensha256 <henshawalice@gmail.com>
---
 .../positionDescriptor bytecode size.snap     |   2 +-
 lib/v4-core                                   |   2 +-
 src/UniswapV4DeployerCompetition.sol          |  85 +++++++++
 .../IUniswapV4DeployerCompetition.sol         |  25 +++
 src/libraries/VanityAddressLib.sol            |  97 ++++++++++
 test/UniswapV4DeployerCompetition.t.sol       | 178 ++++++++++++++++++
 test/libraries/VanityAddressLib.t.sol         | 101 ++++++++++
 .../PositionManager.modifyLiquidities.t.sol   |   2 -
 8 files changed, 488 insertions(+), 4 deletions(-)
 create mode 100644 src/UniswapV4DeployerCompetition.sol
 create mode 100644 src/interfaces/IUniswapV4DeployerCompetition.sol
 create mode 100644 src/libraries/VanityAddressLib.sol
 create mode 100644 test/UniswapV4DeployerCompetition.t.sol
 create mode 100644 test/libraries/VanityAddressLib.t.sol

diff --git a/.forge-snapshots/positionDescriptor bytecode size.snap b/.forge-snapshots/positionDescriptor bytecode size.snap
index 1ee6cee6..4babbfb1 100644
--- a/.forge-snapshots/positionDescriptor bytecode size.snap	
+++ b/.forge-snapshots/positionDescriptor bytecode size.snap	
@@ -1 +1 @@
-31847
\ No newline at end of file
+31728
\ No newline at end of file
diff --git a/lib/v4-core b/lib/v4-core
index 0a849b18..362c9cab 160000
--- a/lib/v4-core
+++ b/lib/v4-core
@@ -1 +1 @@
-Subproject commit 0a849b1810210bc523da73399d6ed8e4480fd3f5
+Subproject commit 362c9cab7c03ca1122795aed2e8e118de9ed186e
diff --git a/src/UniswapV4DeployerCompetition.sol b/src/UniswapV4DeployerCompetition.sol
new file mode 100644
index 00000000..c9af05a6
--- /dev/null
+++ b/src/UniswapV4DeployerCompetition.sol
@@ -0,0 +1,85 @@
+// SPADIX-License-Identifier: UNLICENSED
+pragma solidity 0.8.26;
+
+import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
+import {VanityAddressLib} from "./libraries/VanityAddressLib.sol";
+import {IUniswapV4DeployerCompetition} from "./interfaces/IUniswapV4DeployerCompetition.sol";
+
+/// @title UniswapV4DeployerCompetition
+/// @notice A contract to crowdsource a salt for the best Uniswap V4 address
+contract UniswapV4DeployerCompetition is IUniswapV4DeployerCompetition {
+    using VanityAddressLib for address;
+
+    /// @dev The salt for the best address found so far
+    bytes32 public bestAddressSalt;
+    /// @dev The submitter of the best address found so far
+    address public bestAddressSubmitter;
+
+    /// @dev The deadline for the competition
+    uint256 public immutable competitionDeadline;
+    /// @dev The init code hash of the V4 contract
+    bytes32 public immutable initCodeHash;
+
+    /// @dev The deployer who can initiate the deployment of the v4 PoolManager, until the exclusive deploy deadline.
+    /// @dev After this deadline anyone can deploy.
+    address public immutable deployer;
+    /// @dev The deadline for exclusive deployment by deployer after deadline
+    uint256 public immutable exclusiveDeployDeadline;
+
+    constructor(
+        bytes32 _initCodeHash,
+        uint256 _competitionDeadline,
+        address _exclusiveDeployer,
+        uint256 _exclusiveDeployLength
+    ) {
+        initCodeHash = _initCodeHash;
+        competitionDeadline = _competitionDeadline;
+        exclusiveDeployDeadline = _competitionDeadline + _exclusiveDeployLength;
+        deployer = _exclusiveDeployer;
+    }
+
+    /// @inheritdoc IUniswapV4DeployerCompetition
+    function updateBestAddress(bytes32 salt) external {
+        if (block.timestamp > competitionDeadline) {
+            revert CompetitionOver(block.timestamp, competitionDeadline);
+        }
+
+        address saltSubAddress = address(bytes20(salt));
+        if (saltSubAddress != msg.sender && saltSubAddress != address(0)) revert InvalidSender(salt, msg.sender);
+
+        address newAddress = Create2.computeAddress(salt, initCodeHash);
+        address _bestAddress = bestAddress();
+        if (!newAddress.betterThan(_bestAddress)) {
+            revert WorseAddress(newAddress, _bestAddress, newAddress.score(), _bestAddress.score());
+        }
+
+        bestAddressSalt = salt;
+        bestAddressSubmitter = msg.sender;
+
+        emit NewAddressFound(newAddress, msg.sender, newAddress.score());
+    }
+
+    /// @inheritdoc IUniswapV4DeployerCompetition
+    function deploy(bytes memory bytecode) external {
+        if (keccak256(bytecode) != initCodeHash) {
+            revert InvalidBytecode();
+        }
+
+        if (block.timestamp <= competitionDeadline) {
+            revert CompetitionNotOver(block.timestamp, competitionDeadline);
+        }
+
+        if (msg.sender != deployer && block.timestamp <= exclusiveDeployDeadline) {
+            // anyone can deploy after the deadline
+            revert NotAllowedToDeploy(msg.sender, deployer);
+        }
+
+        // the owner of the contract must be encoded in the bytecode
+        Create2.deploy(0, bestAddressSalt, bytecode);
+    }
+
+    /// @dev returns the best address found so far
+    function bestAddress() public view returns (address) {
+        return Create2.computeAddress(bestAddressSalt, initCodeHash);
+    }
+}
diff --git a/src/interfaces/IUniswapV4DeployerCompetition.sol b/src/interfaces/IUniswapV4DeployerCompetition.sol
new file mode 100644
index 00000000..82959230
--- /dev/null
+++ b/src/interfaces/IUniswapV4DeployerCompetition.sol
@@ -0,0 +1,25 @@
+// SPADIX-License-Identifier: UNLICENSED
+pragma solidity 0.8.26;
+
+/// @title UniswapV4DeployerCompetition
+/// @notice A competition to deploy the UniswapV4 contract with the best address
+interface IUniswapV4DeployerCompetition {
+    event NewAddressFound(address indexed bestAddress, address indexed submitter, uint256 score);
+
+    error InvalidBytecode();
+    error CompetitionNotOver(uint256 currentTime, uint256 deadline);
+    error CompetitionOver(uint256 currentTime, uint256 deadline);
+    error NotAllowedToDeploy(address sender, address deployer);
+    error WorseAddress(address newAddress, address bestAddress, uint256 newScore, uint256 bestScore);
+    error InvalidSender(bytes32 salt, address sender);
+
+    /// @notice Updates the best address if the new address has a better vanity score
+    /// @param salt The salt to use to compute the new address with CREATE2
+    /// @dev The first 20 bytes of the salt must be either address(0) or msg.sender
+    function updateBestAddress(bytes32 salt) external;
+
+    /// @notice deploys the Uniswap v4 PoolManager contract
+    /// @param bytecode The bytecode of the Uniswap v4 PoolManager contract
+    /// @dev The bytecode must match the initCodeHash
+    function deploy(bytes memory bytecode) external;
+}
diff --git a/src/libraries/VanityAddressLib.sol b/src/libraries/VanityAddressLib.sol
new file mode 100644
index 00000000..ba35b969
--- /dev/null
+++ b/src/libraries/VanityAddressLib.sol
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.0;
+
+/// @title VanityAddressLib
+/// @notice A library to score addresses based on their vanity
+library VanityAddressLib {
+    /// @notice Compares two addresses and returns true if the first address has a better vanity score
+    /// @param first The first address to compare
+    /// @param second The second address to compare
+    /// @return better True if the first address has a better vanity score
+    function betterThan(address first, address second) internal pure returns (bool better) {
+        return score(first) > score(second);
+    }
+
+    /// @notice Scores an address based on its vanity
+    /// @dev Scoring rules:
+    ///    Requirement: The first nonzero nibble must be 4
+    ///    10 points for every leading 0 nibble
+    ///    40 points if the first 4 is followed by 3 more 4s
+    ///    20 points if the first nibble after the 4 4s is NOT a 4
+    ///    20 points if the last 4 nibbles are 4s
+    ///    1 point for every 4
+    /// @param addr The address to score
+    /// @return calculatedScore The vanity score of the address
+    function score(address addr) internal pure returns (uint256 calculatedScore) {
+        // convert the address to bytes for easier parsing
+        bytes20 addrBytes = bytes20(addr);
+
+        unchecked {
+            // 10 points per leading zero nibble
+            uint256 leadingZeroCount = getLeadingNibbleCount(addrBytes, 0, 0);
+            calculatedScore += (leadingZeroCount * 10);
+
+            // special handling for 4s immediately after leading 0s
+            uint256 leadingFourCount = getLeadingNibbleCount(addrBytes, leadingZeroCount, 4);
+            // If the first nonzero nibble is not 4, return 0
+            if (leadingFourCount == 0) {
+                return 0;
+            } else if (leadingFourCount == 4) {
+                // 60 points if exactly 4 4s
+                calculatedScore += 60;
+            } else if (leadingFourCount > 4) {
+                // 40 points if more than 4 4s
+                calculatedScore += 40;
+            }
+
+            // handling for remaining nibbles
+            for (uint256 i = 0; i < addrBytes.length * 2; i++) {
+                uint8 currentNibble = getNibble(addrBytes, i);
+
+                // 1 extra point for any 4 nibbles
+                if (currentNibble == 4) {
+                    calculatedScore += 1;
+                }
+            }
+
+            // If the last 4 nibbles are 4s, add 20 points
+            if (addrBytes[18] == 0x44 && addrBytes[19] == 0x44) {
+                calculatedScore += 20;
+            }
+        }
+    }
+
+    /// @notice Returns the number of leading nibbles in an address that match a given value
+    /// @param addrBytes The address to count the leading zero nibbles in
+    function getLeadingNibbleCount(bytes20 addrBytes, uint256 startIndex, uint8 comparison)
+        internal
+        pure
+        returns (uint256 count)
+    {
+        if (startIndex >= addrBytes.length * 2) {
+            return count;
+        }
+
+        for (uint256 i = startIndex; i < addrBytes.length * 2; i++) {
+            uint8 currentNibble = getNibble(addrBytes, i);
+            if (currentNibble != comparison) {
+                return count;
+            }
+            count += 1;
+        }
+    }
+
+    /// @notice Returns the nibble at a given index in an address
+    /// @param input The address to get the nibble from
+    /// @param nibbleIndex The index of the nibble to get
+    function getNibble(bytes20 input, uint256 nibbleIndex) internal pure returns (uint8 currentNibble) {
+        uint8 currByte = uint8(input[nibbleIndex / 2]);
+        if (nibbleIndex % 2 == 0) {
+            // Get the higher nibble of the byte
+            currentNibble = currByte >> 4;
+        } else {
+            // Get the lower nibble of the byte
+            currentNibble = currByte & 0x0F;
+        }
+    }
+}
diff --git a/test/UniswapV4DeployerCompetition.t.sol b/test/UniswapV4DeployerCompetition.t.sol
new file mode 100644
index 00000000..265d3b6f
--- /dev/null
+++ b/test/UniswapV4DeployerCompetition.t.sol
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import {Owned} from "solmate/src/auth/Owned.sol";
+import {Test} from "forge-std/Test.sol";
+import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
+import {UniswapV4DeployerCompetition} from "../src/UniswapV4DeployerCompetition.sol";
+import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
+import {VanityAddressLib} from "../src/libraries/VanityAddressLib.sol";
+import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
+import {IUniswapV4DeployerCompetition} from "../src/interfaces/IUniswapV4DeployerCompetition.sol";
+
+contract UniswapV4DeployerCompetitionTest is Test {
+    using VanityAddressLib for address;
+
+    UniswapV4DeployerCompetition competition;
+    bytes32 initCodeHash;
+    address deployer;
+    address v4Owner;
+    address winner;
+    address defaultAddress;
+    uint256 competitionDeadline;
+    uint256 exclusiveDeployLength = 1 days;
+
+    bytes32 mask20bytes = bytes32(uint256(type(uint96).max));
+
+    function setUp() public {
+        competitionDeadline = block.timestamp + 7 days;
+        v4Owner = makeAddr("V4Owner");
+        winner = makeAddr("Winner");
+        deployer = makeAddr("Deployer");
+        vm.prank(deployer);
+        initCodeHash = keccak256(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner))));
+        competition =
+            new UniswapV4DeployerCompetition(initCodeHash, competitionDeadline, deployer, exclusiveDeployLength);
+        defaultAddress = Create2.computeAddress(bytes32(0), initCodeHash, address(competition));
+    }
+
+    function test_defaultSalt_deploy_succeeds() public {
+        assertEq(competition.bestAddressSubmitter(), address(0));
+        assertEq(competition.bestAddressSalt(), bytes32(0));
+        assertEq(competition.bestAddress(), defaultAddress);
+
+        assertEq(defaultAddress.code.length, 0);
+        vm.warp(competition.competitionDeadline() + 1);
+        vm.prank(deployer);
+        competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner))));
+        assertFalse(defaultAddress.code.length == 0);
+        assertEq(Owned(defaultAddress).owner(), v4Owner);
+    }
+
+    function test_updateBestAddress_succeeds(bytes32 salt) public {
+        salt = (salt & mask20bytes) | bytes32(bytes20(winner));
+
+        assertEq(competition.bestAddressSubmitter(), address(0));
+        assertEq(competition.bestAddressSalt(), bytes32(0));
+        assertEq(competition.bestAddress(), defaultAddress);
+
+        address newAddress = Create2.computeAddress(salt, initCodeHash, address(competition));
+        vm.assume(newAddress.betterThan(defaultAddress));
+
+        vm.prank(winner);
+        vm.expectEmit(true, true, true, false, address(competition));
+        emit IUniswapV4DeployerCompetition.NewAddressFound(newAddress, winner, VanityAddressLib.score(newAddress));
+        competition.updateBestAddress(salt);
+        assertFalse(competition.bestAddress() == address(0), "best address not set");
+        assertEq(competition.bestAddress(), newAddress, "wrong address set");
+        assertEq(competition.bestAddressSubmitter(), winner, "wrong submitter set");
+        assertEq(competition.bestAddressSalt(), salt, "incorrect salt set");
+        address v4Core = competition.bestAddress();
+
+        assertEq(v4Core.code.length, 0);
+        vm.warp(competition.competitionDeadline() + 1);
+        vm.prank(deployer);
+        competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner))));
+        assertFalse(v4Core.code.length == 0);
+        assertEq(Owned(v4Core).owner(), v4Owner);
+        assertEq(address(competition).balance, 0 ether);
+    }
+
+    function test_updateBestAddress_reverts_CompetitionOver(bytes32 salt) public {
+        vm.warp(competition.competitionDeadline() + 1);
+        vm.expectRevert(
+            abi.encodeWithSelector(
+                IUniswapV4DeployerCompetition.CompetitionOver.selector,
+                block.timestamp,
+                competition.competitionDeadline()
+            )
+        );
+        competition.updateBestAddress(salt);
+    }
+
+    function test_updateBestAddress_reverts_InvalidSigner(bytes32 salt) public {
+        vm.assume(bytes20(salt) != bytes20(0));
+        vm.assume(bytes20(salt) != bytes20(winner));
+
+        vm.expectRevert(abi.encodeWithSelector(IUniswapV4DeployerCompetition.InvalidSender.selector, salt, winner));
+        vm.prank(winner);
+        competition.updateBestAddress(salt);
+    }
+
+    function test_updateBestAddress_reverts_WorseAddress(bytes32 salt) public {
+        vm.assume(salt != bytes32(0));
+        salt = (salt & mask20bytes) | bytes32(bytes20(winner));
+
+        address newAddr = Create2.computeAddress(salt, initCodeHash, address(competition));
+        if (!newAddr.betterThan(defaultAddress)) {
+            vm.expectRevert(
+                abi.encodeWithSelector(
+                    IUniswapV4DeployerCompetition.WorseAddress.selector,
+                    newAddr,
+                    competition.bestAddress(),
+                    newAddr.score(),
+                    competition.bestAddress().score()
+                )
+            );
+            vm.prank(winner);
+            competition.updateBestAddress(salt);
+        } else {
+            vm.prank(winner);
+            competition.updateBestAddress(salt);
+            assertEq(competition.bestAddressSubmitter(), winner);
+            assertEq(competition.bestAddressSalt(), salt);
+            assertEq(competition.bestAddress(), newAddr);
+        }
+    }
+
+    function test_deploy_succeeds(bytes32 salt) public {
+        salt = (salt & mask20bytes) | bytes32(bytes20(winner));
+
+        address newAddress = Create2.computeAddress(salt, initCodeHash, address(competition));
+        vm.assume(newAddress.betterThan(defaultAddress));
+
+        vm.prank(winner);
+        competition.updateBestAddress(salt);
+        address v4Core = competition.bestAddress();
+
+        vm.warp(competition.competitionDeadline() + 1);
+        vm.prank(deployer);
+        competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner))));
+        assertFalse(v4Core.code.length == 0);
+        assertEq(Owned(v4Core).owner(), v4Owner);
+        assertEq(TickMath.MAX_TICK_SPACING, type(int16).max);
+    }
+
+    function test_deploy_reverts_CompetitionNotOver(uint256 timestamp) public {
+        vm.assume(timestamp < competition.competitionDeadline());
+        vm.warp(timestamp);
+        vm.expectRevert(
+            abi.encodeWithSelector(
+                IUniswapV4DeployerCompetition.CompetitionNotOver.selector, timestamp, competition.competitionDeadline()
+            )
+        );
+        competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner))));
+    }
+
+    function test_deploy_reverts_InvalidBytecode() public {
+        vm.expectRevert(IUniswapV4DeployerCompetition.InvalidBytecode.selector);
+        vm.prank(deployer);
+        // set the owner as the winner not the correct owner
+        competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(winner))));
+    }
+
+    function test_deploy_reverts_NotAllowedToDeploy() public {
+        vm.warp(competition.competitionDeadline() + 1);
+        vm.prank(address(1));
+        vm.expectRevert(
+            abi.encodeWithSelector(IUniswapV4DeployerCompetition.NotAllowedToDeploy.selector, address(1), deployer)
+        );
+        competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner))));
+    }
+
+    function test_deploy_succeeds_afterExcusiveDeployDeadline() public {
+        vm.warp(competition.exclusiveDeployDeadline() + 1);
+        vm.prank(address(1));
+        competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner))));
+    }
+}
diff --git a/test/libraries/VanityAddressLib.t.sol b/test/libraries/VanityAddressLib.t.sol
new file mode 100644
index 00000000..f9ae5474
--- /dev/null
+++ b/test/libraries/VanityAddressLib.t.sol
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.24;
+
+import {Test} from "forge-std/Test.sol";
+import {VanityAddressLib} from "../../src/libraries/VanityAddressLib.sol";
+
+contract VanityAddressLibTest is Test {
+    function test_fuzz_reasonableScoreNeverReverts(address test) public pure {
+        uint256 score = VanityAddressLib.score(address(test));
+        assertGe(score, 0);
+        assertLe(score, 444);
+    }
+
+    function test_scoreAllFours() public pure {
+        address addr = address(0x4444444444444444444444444444444444444444);
+        uint256 score = VanityAddressLib.score(addr);
+        uint256 expected = 100; // 40 + 40 + 20 = 100
+        assertEq(score, expected);
+    }
+
+    function test_scoreLaterFours() public pure {
+        address addr = address(0x1444444444444444444444444444444444444444);
+        uint256 score = VanityAddressLib.score(addr);
+        uint256 expected = 0; // no leading 4
+        assertEq(score, expected);
+    }
+
+    function test_scoreMixed_4() public pure {
+        address addr = address(0x0044001111111111111111111111111111114114);
+        // counts first null byte
+        // counts first leading 4s after that
+        // does not count future null bytes
+        // counts 4 nibbles after that
+        uint256 score = VanityAddressLib.score(addr);
+        uint256 expected = 24; // 10 * 2 + 2 + 2 = 24
+        assertEq(score, expected);
+    }
+
+    function test_scoreMixed_44() public pure {
+        address addr = address(0x0044001111111111111111111111111111114444);
+        // counts first null byte
+        // counts first leading 4s after that
+        // does not count future null bytes
+        // counts 4 nibbles after that
+        uint256 score = VanityAddressLib.score(addr);
+        uint256 expected = 46; // 10 * 2 + 6 + 20 = 46
+        assertEq(score, expected);
+    }
+
+    function test_scoreMixed_halfZeroHalf4() public pure {
+        address addr = address(0x0004111111111111111111111111111111111111);
+        // counts first null byte
+        // counts first leading 4s after that
+        uint256 score = VanityAddressLib.score(addr);
+        uint256 expected = 31; // 10 * 3 + 1 = 31
+        assertEq(score, expected);
+    }
+
+    function test_scores_succeed() public pure {
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000082)), 0); // 0
+        assertEq(VanityAddressLib.score(address(0x0400000000000000000000000000000000000000)), 11); // 10 * 1 + 1 = 11
+        assertEq(VanityAddressLib.score(address(0x0044000000000000000000000000000000004444)), 46); // 10 * 2 + 6 + 20 = 46
+        assertEq(VanityAddressLib.score(address(0x4444000000000000000000000000000000004444)), 88); // 40 + 20 + 20 + 8 = 88
+        assertEq(VanityAddressLib.score(address(0x0044440000000000000000000000000000000044)), 86); // 10 * 2 + 40 + 20 + 6 = 86
+        assertEq(VanityAddressLib.score(address(0x0000444400000000000000000000000000004444)), 128); // 10 * 4 + 40 + 20 + 20 + 8 = 128
+        assertEq(VanityAddressLib.score(address(0x0040444444444444444444444444444444444444)), 77); // 10 * 2 + 37 + 20 = 77
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000444)), 373); // 10 * 37 + 3 = 373
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000044444444)), 388); // 10 * 32 + 40 + 20 + 8 = 388
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000454444)), 365); // 10 * 34 + 20 + 5 = 365
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000044)), 382); // 10 * 38 + 2 = 382
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000004)), 391); // 10 * 39 + 1 = 391
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000444444)), 406); // 10 * 34 + 40 + 20 + 6 = 406
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000044444)), 415); // 10 * 35 + 40 + 20 + 5 = 415
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000444455)), 404); // 10 * 34 + 40 + 20 + 4 = 404
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000044445)), 414); // 10 * 35 + 40 + 20 + 4 = 414
+        assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000004444)), 444); // 10 * 36 + 40 + 20 + 20 + 4 = 444
+    }
+
+    function test_betterThan() public pure {
+        address addr1 = address(0x0011111111111111111111111111111111111111); // 0 points
+        address addr2 = address(0x4000111111111111111111111111111111111111); // 1 points
+        address addr3 = address(0x0000411111111111111111111111111111111111); // 10 * 4 + 1 = 41 points
+        address addr4 = address(0x0000441111111111111111111111111111111111); // 10 * 4 + 2 = 42 points
+        address addr5 = address(0x0000440011111111111111111111111111111111); // 10 * 4 + 2 = 42 points
+        assertTrue(VanityAddressLib.betterThan(addr2, addr1)); // 1 > 0
+        assertTrue(VanityAddressLib.betterThan(addr3, addr2)); // 41 > 1
+        assertTrue(VanityAddressLib.betterThan(addr3, addr1)); // 41 > 0
+        assertTrue(VanityAddressLib.betterThan(addr4, addr3)); // 42 > 41
+        assertTrue(VanityAddressLib.betterThan(addr4, addr2)); // 42 > 1
+        assertTrue(VanityAddressLib.betterThan(addr4, addr1)); // 42 > 0
+        assertFalse(VanityAddressLib.betterThan(addr5, addr4)); // 42 == 42
+        assertEq(VanityAddressLib.score(addr5), VanityAddressLib.score(addr4)); // 42 == 42
+        assertTrue(VanityAddressLib.betterThan(addr5, addr3)); // 42 > 41
+        assertTrue(VanityAddressLib.betterThan(addr5, addr2)); // 42 > 1
+        assertTrue(VanityAddressLib.betterThan(addr5, addr1)); // 42 > 0
+
+        address addr6 = address(0x0000000000000000000000000000000000004444);
+        address addr7 = address(0x0000000000000000000000000000000000000082);
+        assertTrue(VanityAddressLib.betterThan(addr6, addr7)); // 10 * 36 + 40 + 20 + 20 + 4 = 444 > 0
+    }
+}
diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol
index 6f422ccf..4e14a733 100644
--- a/test/position-managers/PositionManager.modifyLiquidities.t.sol
+++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol
@@ -33,8 +33,6 @@ import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
 import {Planner, Plan} from "../shared/Planner.sol";
 import {DeltaResolver} from "../../src/base/DeltaResolver.sol";
 
-import "forge-std/console2.sol";
-
 contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityFuzzers {
     using StateLibrary for IPoolManager;
     using PoolIdLibrary for PoolKey;

From 3f295d8435e4f776ea2daeb96ce1bc6d63f33fc7 Mon Sep 17 00:00:00 2001
From: Alice <34962750+hensha256@users.noreply.github.com>
Date: Fri, 8 Nov 2024 15:53:55 +0000
Subject: [PATCH 2/2] Update compiler and pull in core (#392)

---
 .forge-snapshots/V4Router_Bytecode.snap                | 2 +-
 .forge-snapshots/positionDescriptor bytecode size.snap | 2 +-
 foundry.toml                                           | 1 +
 lib/v4-core                                            | 2 +-
 4 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap
index 1e50a9b9..7d18bd6a 100644
--- a/.forge-snapshots/V4Router_Bytecode.snap
+++ b/.forge-snapshots/V4Router_Bytecode.snap
@@ -1 +1 @@
-7158
\ No newline at end of file
+7117
\ No newline at end of file
diff --git a/.forge-snapshots/positionDescriptor bytecode size.snap b/.forge-snapshots/positionDescriptor bytecode size.snap
index 4babbfb1..b89166c6 100644
--- a/.forge-snapshots/positionDescriptor bytecode size.snap	
+++ b/.forge-snapshots/positionDescriptor bytecode size.snap	
@@ -1 +1 @@
-31728
\ No newline at end of file
+31687
\ No newline at end of file
diff --git a/foundry.toml b/foundry.toml
index 381668e8..c685a490 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -8,6 +8,7 @@ fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}]
 evm_version = "cancun"
 gas_limit = "3000000000"
 fuzz_runs = 10_000
+bytecode_hash = "none"
 
 [profile.debug]
 via_ir = false
diff --git a/lib/v4-core b/lib/v4-core
index 362c9cab..0f17b65a 160000
--- a/lib/v4-core
+++ b/lib/v4-core
@@ -1 +1 @@
-Subproject commit 362c9cab7c03ca1122795aed2e8e118de9ed186e
+Subproject commit 0f17b65aa61edee384d5129b7ea080f22905faa0