diff --git a/src/libraries/VanityAddressLib.sol b/src/libraries/VanityAddressLib.sol index 9c2d70f0..37ad9b4b 100644 --- a/src/libraries/VanityAddressLib.sol +++ b/src/libraries/VanityAddressLib.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; +import {console2} from "forge-std/console2.sol"; + /// @title VanityAddressLib /// @notice A library to score addresses based on their vanity library VanityAddressLib { @@ -16,72 +18,82 @@ library VanityAddressLib { /// @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 + // 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 + + // convert the address to bytes for easier parsing 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); - } + // 10 points per leading zero nibble + uint256 leadingZeroCount = getLeadingNibbleCount(addrBytes, 0, 0); + calculatedScore += (leadingZeroCount * 10); - // 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; - } + // special handling for 4s immediatley 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; + } - 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; - } + // handling for remaining nibbles + // for (uint256 i = leadingZeroCount + leadingFourCount; i < addrBytes.length * 2; i++) { + for (uint256 i = 0; i < addrBytes.length * 2; i++) { + uint8 currentNibble = getNibble(addrBytes, i); - // count each 4 nibble separately + // 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] & 0xFF == 0x44 && addrBytes[19] & 0xFF == 0x44) { + 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/libraries/VanityAddressLib.t.sol b/test/libraries/VanityAddressLib.t.sol index b284a91b..cd8ea334 100644 --- a/test/libraries/VanityAddressLib.t.sol +++ b/test/libraries/VanityAddressLib.t.sol @@ -5,11 +5,10 @@ import {Test, console} from "forge-std/Test.sol"; import {VanityAddressLib} from "../../src/libraries/VanityAddressLib.sol"; contract VanityAddressLibTest is Test { - function test_scoreAllZeros() public pure { - address addr = address(0); - uint256 score = VanityAddressLib.score(addr); - uint256 expected = 400; // not a 4 after the zeros - assertEq(score, expected); + 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 {