-
Notifications
You must be signed in to change notification settings - Fork 514
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
5 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
'"}' | ||
) | ||
) | ||
) | ||
) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |