Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add UniswapV4DeployerCompetition #117

Merged
merged 31 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d02ba21
feat: add UniswapV4DeployerCompetition
marktoda Jun 4, 2024
246abe3
merge main
dianakocsis Sep 30, 2024
cf4c612
add comments
dianakocsis Oct 4, 2024
5db7a3a
change scoring to discuss
dianakocsis Oct 7, 2024
1af2165
interface plus tests
dianakocsis Oct 7, 2024
e8a4854
inheritdoc tags
dianakocsis Oct 7, 2024
0bbf0dc
change scoring algorithm
dianakocsis Oct 11, 2024
5966068
feat: remove prizes
marktoda Oct 17, 2024
1549749
Merge branch 'main' into add-deployer
marktoda Oct 17, 2024
663b019
feat: add natspec
marktoda Oct 17, 2024
c744963
fix: snaps
marktoda Oct 17, 2024
07657f4
feat: improve test
marktoda Oct 17, 2024
d70e668
feat: cleanup scoring code
marktoda Oct 18, 2024
ef02200
Merge branch 'main' into add-deployer
marktoda Oct 18, 2024
a7bfb24
fix: typo
marktoda Oct 18, 2024
1a27be0
Merge branch 'main' into add-deployer
hensha256 Nov 1, 2024
7ee4dea
update constructor parameters for pool manager
hensha256 Nov 1, 2024
81791ba
correct snapshots
hensha256 Nov 1, 2024
58fa41b
Include address in salt
hensha256 Nov 1, 2024
e56c8b7
remove console logs
hensha256 Nov 1, 2024
5e38cb8
correct test name
hensha256 Nov 1, 2024
f7b60a7
More constructor parameters
hensha256 Nov 1, 2024
46557ec
fix: remove override keywords
marktoda Nov 6, 2024
17a81bf
fix: remove unused code
marktoda Nov 6, 2024
bc20985
fix: use default create2 function signature
marktoda Nov 6, 2024
f788998
feat: remove unused import
marktoda Nov 6, 2024
8055ecb
fix: remove unused v4Owner variable
marktoda Nov 6, 2024
df948e3
fix: add rules to natspec
marktoda Nov 6, 2024
fbd1596
fix(deployComp): remove bestaddress storage
marktoda Nov 7, 2024
1a7f117
fix: minor nits
marktoda Nov 7, 2024
66b91ce
snapshot
hensha256 Nov 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/UniswapV4DeployerCompetition.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPADIX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {Owned} from "solmate/src/auth/Owned.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 best address found so far
address public bestAddress;
/// @dev The submitter of the best address found so far
address public bestAddressSubmitter;

/// @dev The deployer who can initiate the deployment of V4
address public immutable deployer;
/// @dev The owner of the V4 contract
address public immutable v4Owner;
/// @dev The deadline for the competition
uint256 public immutable competitionDeadline;
/// @dev The deadline for exclusive deployment by deployer after deadline
uint256 public immutable exclusiveDeployDeadline;
/// @dev The init code hash of the V4 contract
bytes32 public immutable initCodeHash;

constructor(bytes32 _initCodeHash, address _v4Owner, uint256 _competitionDeadline) {
initCodeHash = _initCodeHash;
v4Owner = _v4Owner;
competitionDeadline = _competitionDeadline;
exclusiveDeployDeadline = _competitionDeadline + 1 days;
deployer = msg.sender;
}

/// @inheritdoc IUniswapV4DeployerCompetition
function updateBestAddress(bytes32 salt) external override {
if (block.timestamp > competitionDeadline) {
revert CompetitionOver(block.timestamp, competitionDeadline);
}

address newAddress = Create2.computeAddress(salt, initCodeHash, address(this));
if (bestAddress != address(0) && !newAddress.betterThan(bestAddress)) {
revert WorseAddress(newAddress, bestAddress, newAddress.score(), bestAddress.score());
}

bestAddress = newAddress;
bestAddressSalt = salt;
bestAddressSubmitter = msg.sender;

emit NewAddressFound(newAddress, msg.sender, newAddress.score());
}

/// @inheritdoc IUniswapV4DeployerCompetition
function deploy(bytes memory bytecode) external override {
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);
}

Create2.deploy(0, bestAddressSalt, bytecode);

// set owner of the pool manager contract
Owned(bestAddress).transferOwnership(v4Owner);
}
}
24 changes: 24 additions & 0 deletions src/interfaces/IUniswapV4DeployerCompetition.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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 InvalidTokenId(uint256 tokenId);

/// @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
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;
}
87 changes: 87 additions & 0 deletions src/libraries/VanityAddressLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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
/// @param addr The address to score
/// @return calculatedScore The vanity score of the address
function score(address addr) internal pure returns (uint256 calculatedScore) {
// 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
bytes20 addrBytes = bytes20(addr);

bool startingZeros = true;
bool startingFours = true;
bool firstFour = true;
uint8 fourCounts; // counter for the number of 4s
// iterate over the nibbles of the address
for (uint256 i = 0; i < addrBytes.length * 2; i++) {
uint8 currentNibble;
if (i % 2 == 0) {
// Get the higher nibble of the byte
currentNibble = uint8(addrBytes[i / 2] >> 4);
} else {
// Get the lower nibble of the byte
currentNibble = uint8(addrBytes[i / 2] & 0x0F);
}

// leading 0s
if (startingZeros && currentNibble == 0) {
calculatedScore += 10;
continue;
} else {
startingZeros = false;
}

// leading 4s
if (startingFours) {
// If the first nonzero nibble is not 4, the score is an automatic 0
if (firstFour && currentNibble != 4) {
return 0;
}

if (currentNibble == 4) {
fourCounts += 1;
if (fourCounts == 4) {
calculatedScore += 40;
// If the leading 4 4s are also the last 4 nibbles, add 20 points
if (i == addrBytes.length * 2 - 1) {
calculatedScore += 20;
}
}
} else {
// If the first nibble after the 4 4s is not a 4, add 20 points
if (fourCounts == 4) {
calculatedScore += 20;
}
startingFours = false;
}
firstFour = false;
}

// count each 4 nibble separately
if (currentNibble == 4) {
calculatedScore += 1;
}
}

// If the last 4 nibbles are 4s, add 20 points
if (addrBytes[18] & 0xFF == 0x44 && addrBytes[19] & 0xFF == 0x44) {
calculatedScore += 20;
}
}
}
146 changes: 146 additions & 0 deletions test/UniswapV4DeployerCompetition.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Owned} from "solmate/src/auth/Owned.sol";
import {Test, console2} 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;
uint256 constant controllerGasLimit = 10000;
uint256 competitionDeadline;

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, controllerGasLimit));
competition = new UniswapV4DeployerCompetition(initCodeHash, v4Owner, competitionDeadline);
assertEq(competition.v4Owner(), v4Owner);
}

function testUpdateBestAddress(bytes32 salt) public {
assertEq(competition.bestAddress(), address(0));
assertEq(competition.bestAddressSubmitter(), address(0));
assertEq(competition.bestAddressSalt(), bytes32(0));

address newAddress = Create2.computeAddress(salt, initCodeHash, address(competition));

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));
assertEq(competition.bestAddress(), newAddress);
assertEq(competition.bestAddressSubmitter(), winner);
assertEq(competition.bestAddressSalt(), salt);
address v4Core = competition.bestAddress();

assertEq(v4Core.code.length, 0);
vm.warp(competition.competitionDeadline() + 1);
vm.prank(deployer);
competition.deploy(abi.encodePacked(type(PoolManager).creationCode, controllerGasLimit));
assertFalse(v4Core.code.length == 0);
assertEq(Owned(v4Core).owner(), v4Owner);
assertEq(address(competition).balance, 0 ether);
}

function testCompetitionOver(bytes32 salt) public {
vm.warp(competition.competitionDeadline() + 1);
vm.expectRevert(
abi.encodeWithSelector(
IUniswapV4DeployerCompetition.CompetitionOver.selector,
block.timestamp,
competition.competitionDeadline()
)
);
competition.updateBestAddress(salt);
}

function testUpdateBestAddressOpen(bytes32 salt) public {
vm.prank(winner);
competition.updateBestAddress(salt);
address v4Core = competition.bestAddress();

vm.warp(competition.competitionDeadline() + 1.1 days);
competition.deploy(abi.encodePacked(type(PoolManager).creationCode, controllerGasLimit));
assertFalse(v4Core.code.length == 0);
assertEq(Owned(v4Core).owner(), v4Owner);
assertEq(TickMath.MAX_TICK_SPACING, type(int16).max);
}

function testCompetitionNotOver(bytes32 salt, uint256 timestamp) public {
vm.assume(timestamp < competition.competitionDeadline());
vm.prank(winner);
competition.updateBestAddress(salt);
vm.warp(timestamp);
vm.expectRevert(
abi.encodeWithSelector(
IUniswapV4DeployerCompetition.CompetitionNotOver.selector, timestamp, competition.competitionDeadline()
)
);
competition.deploy(abi.encodePacked(type(PoolManager).creationCode, controllerGasLimit));
}

function testInvalidBytecode(bytes32 salt) public {
vm.prank(winner);
competition.updateBestAddress(salt);
vm.expectRevert(IUniswapV4DeployerCompetition.InvalidBytecode.selector);
competition.deploy(abi.encodePacked(type(PoolManager).creationCode, controllerGasLimit + 1));
}

function testInvalidMsgSender(bytes32 salt) public {
vm.prank(winner);
competition.updateBestAddress(salt);
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, controllerGasLimit));
}

function testAfterExcusiveDeployDeadline(bytes32 salt) public {
vm.prank(winner);
competition.updateBestAddress(salt);
vm.warp(competition.exclusiveDeployDeadline() + 1);
vm.prank(address(1));
competition.deploy(abi.encodePacked(type(PoolManager).creationCode, controllerGasLimit));
}

function testEqualSaltNotChanged(bytes32 salt) public {
vm.prank(winner);
competition.updateBestAddress(salt);
assertFalse(competition.bestAddress() == address(0));
assertEq(competition.bestAddressSubmitter(), winner);
assertEq(competition.bestAddressSalt(), salt);

address newAddr = Create2.computeAddress(salt >> 1, initCodeHash, address(competition));
vm.assume(competition.bestAddress().betterThan(newAddr));

vm.prank(address(1));
vm.expectRevert(
abi.encodeWithSelector(
IUniswapV4DeployerCompetition.WorseAddress.selector,
newAddr,
competition.bestAddress(),
newAddr.score(),
competition.bestAddress().score()
)
);
competition.updateBestAddress(salt >> 1);
}
}
Loading
Loading