Skip to content

Commit

Permalink
feat: add UniswapV4DeployerCompetition
Browse files Browse the repository at this point in the history
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
  • Loading branch information
marktoda committed Jun 4, 2024
1 parent 5082779 commit d02ba21
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 0 deletions.
78 changes: 78 additions & 0 deletions contracts/UniswapV4DeployerCompetition.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPADIX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {Create2} from "openzeppelin-contracts/contracts/utils/Create2.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {ERC721} from "solmate/tokens/ERC721.sol";
import {TokenURILib} from "./libraries/UniswapV4DeployerTokenURILib.sol";
import {VanityAddressLib} from "./libraries/VanityAddressLib.sol";

contract UniswapV4DeployerCompetition is ERC721 {
using VanityAddressLib for address;

event NewAddressFound(address bestAddress, address minter, uint256 score);

error InvalidBytecode();
error CompetitionNotOver();
error CompetitionOver();
error NotAllowedToDeploy();
error BountyTransferFailed();
error WorseAddress();

bytes32 public bestAddressSalt;
address public bestAddress;
address public bestAddressSender;

address public immutable v4Owner;
uint256 public immutable competitionDeadline = block.timestamp + 7 days;
uint256 public immutable exclusiveDeployDeadline = competitionDeadline + 1 days;
bytes32 public immutable initCodeHash;

constructor(bytes32 _initCodeHash, address _v4Owner) payable ERC721("UniswapV4 Deployer", "V4D") {
initCodeHash = _initCodeHash;
v4Owner = _v4Owner;
}

function updateBestAddress(bytes32 salt) external {
if (block.timestamp > competitionDeadline) {
revert CompetitionOver();
}
address newAddress = Create2.computeAddress(salt, initCodeHash, address(this));
if (bestAddress != address(0) && !newAddress.betterThan(bestAddress)) {
revert WorseAddress();
}

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

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

function deploy(bytes memory bytecode) external {
if (keccak256(bytecode) != initCodeHash) {
revert InvalidBytecode();
}
if (block.timestamp < competitionDeadline) {
revert CompetitionNotOver();
}
if (msg.sender != bestAddressSender && block.timestamp < exclusiveDeployDeadline) {
revert NotAllowedToDeploy(); // anyone can deploy after the deadline
}
Create2.deploy(0, bestAddressSalt, bytecode);

// mint to winner
_mint(bestAddressSender, 0);
// transfer the bounty to winner
(bool success,) = bestAddressSender.call{value: address(this).balance}("");
if (!success) {
revert BountyTransferFailed();
}
// set owner
Owned(bestAddress).transferOwnership(v4Owner);
}

function tokenURI(uint256) public pure override returns (string memory) {
return TokenURILib.tokenURI();
}
}
27 changes: 27 additions & 0 deletions contracts/libraries/UniswapV4DeployerTokenURILib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol";

library TokenURILib {
function tokenURI() internal pure returns (string memory) {
return string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(
bytes(
abi.encodePacked(
'{"name":"',
"Uniswap V4 Deployer",
'", "description":"',
"I deployed the UniswapV4 contract with a sick address",
'", "image": "',
"ipfs://QmTZeKgupCJNwMek2AoNEYR1pmjqYiS6MgddadH3RKPXvA/v4nft.svg",
'"}'
)
)
)
)
);
}
}
41 changes: 41 additions & 0 deletions contracts/libraries/VanityAddressLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

library VanityAddressLib {
function betterThan(address first, address second) internal pure returns (bool better) {
return score(first) > score(second);
}

function score(address addr) internal pure returns (uint256 calculatedScore) {
// 10 points for every leading 0 byte
// 1 point for every 4 after that
bytes20 addrBytes = bytes20(addr);

bool startingZeros = true;
bool startingFours = true;
for (uint256 i = 0; i < 20; i++) {
if (startingZeros && addrBytes[i] == 0x00) {
calculatedScore += 20;
continue;
} else {
startingZeros = false;
}
if (startingFours && addrBytes[i] == 0x44) {
calculatedScore += 5;
continue;
} else {
startingFours = false;
}

if (!startingZeros && !startingFours) {
// count each nibble separately
if (addrBytes[i] & 0x0F == 0x04) {
calculatedScore += 1;
}
if (addrBytes[i] & 0xF0 == 0x40) {
calculatedScore += 1;
}
}
}
}
}
103 changes: 103 additions & 0 deletions test/UniswapV4DeployerCompetition.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {Owned} from "solmate/auth/Owned.sol";
import {Test, console2} from "forge-std/Test.sol";
import {PoolManager} from "v4-core/PoolManager.sol";
import {UniswapV4DeployerCompetition} from "../contracts/UniswapV4DeployerCompetition.sol";

contract UniswapV4DeployerCompetitionTest is Test {
UniswapV4DeployerCompetition deployer;
bytes32 initCodeHash;
address v4Owner;
address winner;
uint256 constant controllerGasLimit = 10000;

function setUp() public {
v4Owner = makeAddr("V4Owner");
winner = makeAddr("Winner");
initCodeHash = keccak256(abi.encodePacked(type(PoolManager).creationCode, controllerGasLimit));
deployer = new UniswapV4DeployerCompetition{value: 1 ether}(initCodeHash, v4Owner);
}

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

vm.prank(winner);
deployer.updateBestAddress(salt);
assertEq(address(deployer).balance, 1 ether);
assertFalse(deployer.bestAddress() == address(0));
assertEq(deployer.bestAddressSender(), winner);
assertEq(deployer.bestAddressSalt(), salt);
address v4Core = deployer.bestAddress();

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

function testCompetitionOver(bytes32 salt) public {
vm.warp(deployer.competitionDeadline() + 1);
vm.expectRevert(UniswapV4DeployerCompetition.CompetitionOver.selector);
deployer.updateBestAddress(salt);
}

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

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

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

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

function testEqualSaltNotChanged(bytes32 salt) public {
vm.prank(winner);
deployer.updateBestAddress(salt);
assertEq(deployer.bestAddressSender(), winner);
assertEq(deployer.bestAddressSalt(), salt);

vm.prank(address(1));
vm.expectRevert(UniswapV4DeployerCompetition.WorseAddress.selector);
deployer.updateBestAddress(salt);
}

function testUpdateNotEqual() public {
bytes32 salt1 = keccak256(abi.encodePacked(uint256(1)));
bytes32 salt2 = keccak256(abi.encodePacked(uint256(2)));
vm.prank(winner);
deployer.updateBestAddress(salt1);
vm.prank(winner);
deployer.updateBestAddress(salt2);
assertFalse(deployer.bestAddress() == address(0));
assertEq(deployer.bestAddressSender(), winner);
assertEq(deployer.bestAddressSalt(), salt2);
}
}
59 changes: 59 additions & 0 deletions test/libraries/VanityAddressLib.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {Test, console} from "forge-std/Test.sol";
import {VanityAddressLib} from "../../contracts/libraries/VanityAddressLib.sol";

contract VanityAddressLibTest is Test {
// function testScore(uint8 numZerosStart, uint8 numFoursStart, uint8 numOtherFours) public {
function testScoreAllZeros() public {
address addr = address(0);
uint256 score = VanityAddressLib.score(addr);
uint256 expected = 400; // 20 * 10
assertEq(score, expected);
}

function testScoreAllFours() public {
address addr = address(0x4444444444444444444444444444444444444444);
uint256 score = VanityAddressLib.score(addr);
uint256 expected = 100; // 20 * 5
assertEq(score, expected);
}

function testScoreLaterFours() public {
address addr = address(0x1444444444444444444444444444444444444444);
uint256 score = VanityAddressLib.score(addr);
uint256 expected = 39; // 20 + 19
assertEq(score, expected);
}

function testScoreMixed() public {
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 = 27; // 10+5+1+1
assertEq(score, expected);
}

function testBetterThan() public {
address addr1 = address(0x0011111111111111111111111111111111111111);
address addr2 = address(0x0000111111111111111111111111111111111111);
address addr3 = address(0x0000411111111111111111111111111111111111);
address addr4 = address(0x0000441111111111111111111111111111111111);
address addr5 = address(0x0000440011111111111111111111111111111111);
assertTrue(VanityAddressLib.betterThan(addr2, addr1));
assertTrue(VanityAddressLib.betterThan(addr3, addr2));
assertTrue(VanityAddressLib.betterThan(addr3, addr1));
assertTrue(VanityAddressLib.betterThan(addr4, addr3));
assertTrue(VanityAddressLib.betterThan(addr4, addr2));
assertTrue(VanityAddressLib.betterThan(addr4, addr1));
assertFalse(VanityAddressLib.betterThan(addr5, addr4));
assertEq(VanityAddressLib.score(addr5), VanityAddressLib.score(addr4));
assertTrue(VanityAddressLib.betterThan(addr5, addr3));
assertTrue(VanityAddressLib.betterThan(addr5, addr2));
assertTrue(VanityAddressLib.betterThan(addr5, addr1));
}
}

0 comments on commit d02ba21

Please sign in to comment.