Merge pull request #90 from lens-protocol/test/token-handle-registry

Test/token handle registry
This commit is contained in:
Alan
2023-06-06 18:22:40 +01:00
committed by GitHub
9 changed files with 1125 additions and 25 deletions

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady

View File

@@ -89,12 +89,19 @@ contract LensHandles is ERC721, ImmutableOwnable, ILensHandles {
return NAMESPACE_HASH;
}
// TODO: Should we revert if it doesn't exist?
function getLocalName(uint256 tokenId) public view returns (string memory) {
string memory localName = _localNames[tokenId];
if (bytes(localName).length == 0) {
revert HandlesErrors.DoesNotExist();
}
return _localNames[tokenId];
}
// TODO: Should we revert if it doesn't exist?
function getHandle(uint256 tokenId) public view returns (string memory) {
return string.concat(_localNames[tokenId], '.', NAMESPACE);
string memory localName = getLocalName(tokenId);
return string.concat(localName, '.', NAMESPACE);
}
function getTokenId(string memory localName) public pure returns (uint256) {
@@ -106,19 +113,21 @@ contract LensHandles is ERC721, ImmutableOwnable, ILensHandles {
//////////////////////////////////////
function _validateLocalName(string memory localName) internal view {
uint256 localNameLength = bytes(localName).length;
bytes memory localNameAsBytes = bytes(localName);
uint256 localNameLength = localNameAsBytes.length;
if (localNameLength == 0 || localNameLength + SEPARATOR_LENGTH + NAMESPACE_LENGTH > MAX_HANDLE_LENGTH) {
revert HandlesErrors.HandleLengthInvalid();
}
bytes1 firstByte = bytes(localName)[0];
bytes1 firstByte = localNameAsBytes[0];
if (firstByte == '-' || firstByte == '_') {
revert HandlesErrors.HandleFirstCharInvalid();
}
uint256 i;
while (i < localNameLength) {
if (bytes(localName)[i] == '.') {
if (!_isAlphaNumeric(localNameAsBytes[i]) && localNameAsBytes[i] != '-' && localNameAsBytes[i] != '_') {
revert HandlesErrors.HandleContainsInvalidCharacters();
}
unchecked {
@@ -126,4 +135,8 @@ contract LensHandles is ERC721, ImmutableOwnable, ILensHandles {
}
}
}
function _isAlphaNumeric(bytes1 char) internal pure returns (bool) {
return (char >= '0' && char <= '9') || (char >= 'a' && char <= 'z');
}
}

View File

@@ -39,26 +39,14 @@ contract TokenHandleRegistry is ITokenHandleRegistry {
_;
}
modifier onlyHandleOrTokenOwner(
uint256 handleId,
uint256 tokenId,
address transactionExecutor
) {
if (
IERC721(LENS_HANDLES).ownerOf(handleId) != transactionExecutor &&
IERC721(LENS_HUB).ownerOf(tokenId) != transactionExecutor
) {
revert RegistryErrors.NotHandleNorTokenOwner();
}
_;
}
constructor(address lensHub, address lensHandles) {
LENS_HUB = lensHub;
LENS_HANDLES = lensHandles;
}
// Lens V1 to Lens V2 migration function
// WARNING: It is able to link the Token and Handle even if they're not in the same wallet.
// But it is designed to be only called from LensHub migration function, which assures that they are.
function migrationLink(uint256 handleId, uint256 tokenId) external {
if (msg.sender != LENS_HUB) {
revert RegistryErrors.OnlyLensHub();
@@ -81,9 +69,21 @@ contract TokenHandleRegistry is ITokenHandleRegistry {
}
/// @inheritdoc ITokenHandleRegistry
function unlink(uint256 handleId, uint256 tokenId) external onlyHandleOrTokenOwner(handleId, tokenId, msg.sender) {
function unlink(uint256 handleId, uint256 tokenId) external {
// We revert here only in the case if both tokens exists and the caller is not the owner of any of them
if (
ILensHandles(LENS_HANDLES).exists(handleId) &&
ILensHandles(LENS_HANDLES).ownerOf(handleId) != msg.sender &&
ILensHub(LENS_HUB).exists(tokenId) &&
ILensHub(LENS_HUB).ownerOf(tokenId) != msg.sender
) {
revert RegistryErrors.NotHandleNorTokenOwner();
}
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: LENS_HANDLES, id: handleId});
RegistryTypes.Token memory tokenPointedByHandle = handleToToken[_handleHash(handle)];
// We check if the tokens are (were) linked for the case if some of them doesn't exist
if (tokenPointedByHandle.id != tokenId) {
revert RegistryErrors.NotLinked();
}
@@ -115,11 +115,6 @@ contract TokenHandleRegistry is ITokenHandleRegistry {
return defaultHandleId;
}
// TODO: Add this to interface and make it prettier
function isLinked(uint256 handleId, uint256 tokenId) external view returns (bool) {
return this.resolve(handleId) == tokenId && this.getDefaultHandle(tokenId) == handleId;
}
//////////////////////////////////////
/// INTERNAL FUNCTIONS ///
//////////////////////////////////////

View File

@@ -17,4 +17,5 @@ library HandlesErrors {
error HandleFirstCharInvalid();
error NotOwnerNorWhitelisted();
error NotOwner();
error DoesNotExist();
}

1
lib/solady Submodule

Submodule lib/solady added at 6675f94f3f

View File

@@ -11,3 +11,5 @@ operator-filter-registry/=lib/seadrop/lib/operator-filter-registry/src/
solmate/=lib/seadrop/lib/solmate/src/
utility-contracts/=lib/seadrop/lib/utility-contracts/src/
create2-scripts/=lib/seadrop/lib/create2-helpers/script/
solady/=lib/solady/src/

View File

@@ -156,6 +156,7 @@ contract ProxyAdminTest is BaseTest {
assertEq(hub.ownerOf(profileId), profileOwner, 'Profile owner mismatch');
assertEq(lensHandles.ownerOf(handleId), profileOwner, 'Handle owner mismatch');
assertTrue(tokenHandleRegistry.isLinked(handleId, profileId), 'Handle not linked to profile');
assertEq(tokenHandleRegistry.resolve(handleId), profileId, 'Handle not linked to profile');
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId, 'Profile not linked to handle');
}
}

View File

@@ -0,0 +1,354 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import 'test/base/BaseTest.t.sol';
import {LibString} from 'solady/utils/LibString.sol';
import {Base64} from 'solady/utils/Base64.sol';
import {HandlesErrors} from 'contracts/namespaces/constants/Errors.sol';
import {HandlesEvents} from 'contracts/namespaces/constants/Events.sol';
contract LensHandlesTest is BaseTest {
using stdJson for string;
uint256 constant MAX_HANDLE_LENGTH = 26;
function setUp() public override {
super.setUp();
}
// NEGATIVES
function testCannot_GetTokenURI_IfNotMinted(uint256 tokenId) public {
vm.assume(!lensHandles.exists(tokenId));
vm.expectRevert('ERC721: invalid token ID');
lensHandles.tokenURI(tokenId);
}
function testCannot_Burn_IfNotOwnerOf(address owner, address otherAddress) public {
vm.assume(owner != otherAddress);
vm.assume(owner != address(0));
vm.assume(otherAddress != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(lensHandles), ADMIN_SLOT))));
vm.assume(otherAddress != proxyAdmin);
string memory handle = 'handle';
vm.prank(address(hub));
uint256 handleId = lensHandles.mintHandle(owner, handle);
assertTrue(lensHandles.exists(handleId));
assertEq(lensHandles.ownerOf(handleId), owner);
vm.expectRevert(HandlesErrors.NotOwner.selector);
vm.prank(otherAddress);
lensHandles.burn(handleId);
}
function testCannot_MintHandle_IfNotOwnerOrHubOrWhitelistedProfileCreator(address otherAddress) public {
vm.assume(otherAddress != address(0));
vm.assume(otherAddress != address(hub));
vm.assume(otherAddress != lensHandles.OWNER());
vm.assume(!hub.isProfileCreatorWhitelisted(otherAddress));
address proxyAdmin = address(uint160(uint256(vm.load(address(lensHandles), ADMIN_SLOT))));
vm.assume(otherAddress != proxyAdmin);
string memory handle = 'handle';
vm.expectRevert(HandlesErrors.NotOwnerNorWhitelisted.selector);
vm.prank(otherAddress);
lensHandles.mintHandle(otherAddress, handle);
}
function testCannot_MintHandle_WithZeroLength() public {
vm.expectRevert(HandlesErrors.HandleLengthInvalid.selector);
vm.prank(address(hub));
lensHandles.mintHandle(address(this), '');
}
function testCannot_MintHandle_WithNonUniqueLocalName() public {
vm.prank(address(hub));
lensHandles.mintHandle(address(this), 'handle');
vm.expectRevert('ERC721: token already minted');
vm.prank(address(hub));
lensHandles.mintHandle(makeAddr('ANOTHER_ADDRESS'), 'handle');
}
function testCannot_MintHandle_WithLengthMoreThanMax(uint256 randomFuzz) public {
string memory randomHandle = _randomAlphanumericString(MAX_HANDLE_LENGTH + 1, randomFuzz);
vm.expectRevert(HandlesErrors.HandleLengthInvalid.selector);
vm.prank(address(hub));
lensHandles.mintHandle(address(this), randomHandle);
}
function testCannot_MintHandle_WithInvalidFirstChar(uint256 length, uint256 randomFuzz) public {
length = bound(length, 0, MAX_HANDLE_LENGTH - 1); // we will add 1 char at the start, so length is shorter by 1
string memory randomHandle = _randomAlphanumericString(length, randomFuzz);
string memory invalidUnderscoreHandle = string.concat('_', randomHandle);
vm.expectRevert(HandlesErrors.HandleFirstCharInvalid.selector);
vm.prank(address(hub));
lensHandles.mintHandle(address(this), invalidUnderscoreHandle);
string memory invalidDashHandle = string.concat('-', randomHandle);
vm.expectRevert(HandlesErrors.HandleFirstCharInvalid.selector);
vm.prank(address(hub));
lensHandles.mintHandle(address(this), invalidDashHandle);
}
function testCannot_MintHandle_WithInvalidChar(
uint256 length,
uint256 insertionPosition,
uint256 randomFuzz,
uint256 invalidCharCode
) public {
length = bound(length, 1, MAX_HANDLE_LENGTH);
insertionPosition = bound(insertionPosition, 0, length - 1);
invalidCharCode = bound(invalidCharCode, 0x00, 0xFF);
vm.assume(
(invalidCharCode < 48 || // '0'
invalidCharCode > 122 || // 'z'
(invalidCharCode > 57 && invalidCharCode < 97)) && // '9' and 'a'
invalidCharCode != 45 && // '-'
invalidCharCode != 95 // '_'
);
string memory randomHandle = _randomAlphanumericString(length, randomFuzz);
console.log('randomHandle:', randomHandle);
console.log('insert position:', insertionPosition);
console.log('invalid char code:', invalidCharCode);
bytes memory randomHandleBytes = bytes(randomHandle);
randomHandleBytes[insertionPosition] = bytes1(uint8(invalidCharCode));
string memory invalidHandle = string(randomHandleBytes);
console.log('invalidHandle', invalidHandle);
vm.expectRevert(HandlesErrors.HandleContainsInvalidCharacters.selector);
vm.prank(address(hub));
lensHandles.mintHandle(address(this), invalidHandle);
}
// SCENARIOS
function testName() public {
string memory name = lensHandles.name();
assertEq(name, '.lens Handles');
}
function testSymbol() public {
string memory symbol = lensHandles.symbol();
assertEq(symbol, '.lens');
}
function testExists(uint256 number) public {
number = bound(number, 1, 10 ** (MAX_HANDLE_LENGTH) - 1);
string memory numbersHandle = vm.toString(number);
uint256 expectedTokenId = lensHandles.getTokenId(numbersHandle);
vm.assume(!lensHandles.exists(expectedTokenId));
vm.prank(address(hub));
uint256 handleId = lensHandles.mintHandle(address(this), numbersHandle);
assertEq(handleId, expectedTokenId);
assertTrue(lensHandles.exists(handleId));
lensHandles.burn(handleId);
assertFalse(lensHandles.exists(handleId));
}
function testGetNamespace() public {
string memory namespace = lensHandles.getNamespace();
assertEq(namespace, 'lens');
}
function testGetNamespaceHash() public {
string memory namespace = lensHandles.getNamespace();
bytes32 namespaceHash = lensHandles.getNamespaceHash();
assertEq(namespaceHash, keccak256(bytes(namespace)));
}
function testConstructionImmutables(address owner, address hub) public {
LensHandles newLensHandles = new LensHandles(owner, hub);
assertEq(newLensHandles.OWNER(), owner);
assertEq(newLensHandles.LENS_HUB(), hub);
}
// TODO: Should we revert if it doesn't exist?
function testGetLocalName(uint256 number) public {
number = bound(number, 1, 10 ** (MAX_HANDLE_LENGTH) - 1);
string memory numbersHandle = vm.toString(number);
uint256 expectedTokenId = lensHandles.getTokenId(numbersHandle);
vm.assume(!lensHandles.exists(expectedTokenId));
vm.expectRevert(HandlesErrors.DoesNotExist.selector);
lensHandles.getLocalName(expectedTokenId);
vm.prank(address(hub));
uint256 handleId = lensHandles.mintHandle(address(this), numbersHandle);
assertEq(handleId, expectedTokenId);
string memory localName = lensHandles.getLocalName(handleId);
assertEq(localName, numbersHandle);
lensHandles.burn(handleId);
vm.expectRevert(HandlesErrors.DoesNotExist.selector);
lensHandles.getLocalName(expectedTokenId);
}
// TODO: Should we revert if it doesn't exist?
function testGetHandle(uint256 number) public {
number = bound(number, 1, 10 ** (MAX_HANDLE_LENGTH) - 1);
string memory numbersHandle = vm.toString(number);
uint256 expectedTokenId = lensHandles.getTokenId(numbersHandle);
vm.assume(!lensHandles.exists(expectedTokenId));
vm.expectRevert(HandlesErrors.DoesNotExist.selector);
lensHandles.getHandle(expectedTokenId);
vm.prank(address(hub));
uint256 handleId = lensHandles.mintHandle(address(this), numbersHandle);
assertEq(handleId, expectedTokenId);
string memory namespaceSuffix = string.concat('.', lensHandles.getNamespace());
string memory handle = lensHandles.getHandle(handleId);
assertEq(handle, string.concat(numbersHandle, namespaceSuffix));
lensHandles.burn(handleId);
vm.expectRevert(HandlesErrors.DoesNotExist.selector);
lensHandles.getHandle(expectedTokenId);
}
function testGetTokenId(uint256 number) public {
number = bound(number, 1, 10 ** (MAX_HANDLE_LENGTH) - 1);
string memory numbersHandle = vm.toString(number);
uint256 expectedTokenId = uint256(keccak256(bytes(numbersHandle)));
assertEq(lensHandles.getTokenId(numbersHandle), expectedTokenId);
}
function testTokenURI() public {
string memory handle = 'handle';
vm.prank(address(hub));
uint256 handleId = lensHandles.mintHandle(address(this), handle);
string memory tokenURI = lensHandles.tokenURI(handleId);
string memory base64prefix = 'data:application/json;base64,';
string memory decodedTokenURI = string(Base64.decode(LibString.slice(tokenURI, bytes(base64prefix).length)));
assertEq(decodedTokenURI.readString('.name'), string.concat('@', handle));
assertEq(decodedTokenURI.readString('.description'), string.concat('Lens Protocol - @', handle));
assertEq(decodedTokenURI.readUint('.attributes[0].value'), handleId);
assertEq(decodedTokenURI.readString('.attributes[1].value'), lensHandles.symbol());
assertEq(decodedTokenURI.readUint('.attributes[2].value'), bytes(handle).length);
}
function testBurn(address owner) public {
vm.assume(owner != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(lensHandles), ADMIN_SLOT))));
vm.assume(owner != proxyAdmin);
string memory handle = 'handle';
vm.prank(address(hub));
uint256 handleId = lensHandles.mintHandle(owner, handle);
assertTrue(lensHandles.exists(handleId));
assertEq(lensHandles.ownerOf(handleId), owner);
vm.prank(owner);
lensHandles.burn(handleId);
assertFalse(lensHandles.exists(handleId));
vm.expectRevert(HandlesErrors.DoesNotExist.selector);
lensHandles.getLocalName(handleId);
}
function testMintHandle_IfOwner(address to, uint256 length, uint256 randomFuzz) public {
_mintHandle(lensHandles.OWNER(), to, length, randomFuzz);
}
function testMintHandle_ifHub(address to, uint256 length, uint256 randomFuzz) public {
_mintHandle(address(hub), to, length, randomFuzz);
}
function testMintHandle_ifWhitelistedProfileCreator(
address whitelistedProfileCreator,
address to,
uint256 length,
uint256 randomFuzz
) public {
vm.assume(whitelistedProfileCreator != address(0));
vm.assume(whitelistedProfileCreator != address(hub));
vm.assume(whitelistedProfileCreator != lensHandles.OWNER());
address proxyAdmin = address(uint160(uint256(vm.load(address(lensHandles), ADMIN_SLOT))));
vm.assume(whitelistedProfileCreator != proxyAdmin);
vm.prank(governance);
hub.whitelistProfileCreator(whitelistedProfileCreator, true);
_mintHandle(whitelistedProfileCreator, to, length, randomFuzz);
}
function _mintHandle(address minter, address to, uint256 length, uint256 randomFuzz) internal {
vm.assume(to != address(0));
length = bound(length, 1, MAX_HANDLE_LENGTH);
string memory randomHandle = _randomAlphanumericString(length, randomFuzz);
uint256 expectedHandleId = lensHandles.getTokenId(randomHandle);
vm.expectEmit(true, true, true, true, address(lensHandles));
emit HandlesEvents.HandleMinted(
randomHandle,
lensHandles.getNamespace(),
expectedHandleId,
to,
block.timestamp
);
vm.prank(minter);
uint256 handleId = lensHandles.mintHandle(to, randomHandle);
assertEq(handleId, expectedHandleId);
assertEq(lensHandles.ownerOf(handleId), to);
string memory localName = lensHandles.getLocalName(handleId);
assertEq(localName, randomHandle);
}
function _randomAlphanumericString(uint256 length, uint256 randomFuzz) internal view returns (string memory) {
bytes memory allowedChars = '0123456789abcdefghijklmnopqrstuvwxyz_-';
string memory str = '';
for (uint256 i = 0; i < length; i++) {
uint8 charCode = uint8((randomFuzz >> (i * 8)) & 0xff);
charCode = uint8(bound(charCode, 0, allowedChars.length - 1));
if (i == 0 && (allowedChars[charCode] == '_' || allowedChars[charCode] == '-')) {
charCode /= 2;
}
string memory char = string(abi.encodePacked(allowedChars[charCode]));
str = string.concat(str, char);
}
return str;
}
}

View File

@@ -0,0 +1,730 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import 'test/base/BaseTest.t.sol';
import {RegistryErrors} from 'contracts/namespaces/constants/Errors.sol';
import {RegistryEvents} from 'contracts/namespaces/constants/Events.sol';
import {RegistryTypes} from 'contracts/namespaces/constants/Types.sol';
contract TokenHandleRegistryTest is BaseTest {
uint256 profileId;
uint256 handleId;
address initialProfileHolder = makeAddr('INITIAL_PROFILE_HOLDER');
address initialHandleHolder = makeAddr('INITIAL_HANDLE_HOLDER');
function setUp() public override {
super.setUp();
profileId = _createProfile(initialProfileHolder);
vm.prank(governance);
handleId = lensHandles.mintHandle(initialHandleHolder, 'handle');
}
function testCannot_MigrationLink_IfNotHub(address otherAddress) public {
vm.assume(otherAddress != address(hub));
vm.assume(otherAddress != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(otherAddress != proxyAdmin);
vm.expectRevert(RegistryErrors.OnlyLensHub.selector);
vm.prank(otherAddress);
tokenHandleRegistry.migrationLink(profileId, handleId);
}
function testCannot_Link_IfNotHoldingProfile(address otherAddress) public {
vm.assume(otherAddress != hub.ownerOf(profileId));
vm.assume(otherAddress != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(otherAddress != proxyAdmin);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, otherAddress, handleId);
vm.expectRevert(RegistryErrors.NotTokenOwner.selector);
vm.prank(otherAddress);
tokenHandleRegistry.link(handleId, profileId);
}
function testCannot_Link_IfNotHoldingHandle(address otherAddress) public {
vm.assume(otherAddress != lensHandles.ownerOf(handleId));
vm.assume(otherAddress != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(otherAddress != proxyAdmin);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, otherAddress, profileId);
vm.expectRevert(RegistryErrors.NotHandleOwner.selector);
vm.prank(otherAddress);
tokenHandleRegistry.link(handleId, profileId);
}
function testCannot_Link_IfHandleDoesNotExist(uint256 nonexistingHandleId) public {
vm.assume(!lensHandles.exists(nonexistingHandleId));
vm.expectRevert('ERC721: invalid token ID');
vm.prank(initialProfileHolder);
tokenHandleRegistry.link(nonexistingHandleId, profileId);
}
function testCannot_Link_IfProfileDoesNotExist(uint256 nonexistingProfileId) public {
vm.assume(!hub.exists(nonexistingProfileId));
vm.expectRevert(Errors.TokenDoesNotExist.selector);
vm.prank(initialHandleHolder);
tokenHandleRegistry.link(handleId, nonexistingProfileId);
}
function testCannot_Unlink_IfNotHoldingProfileOrHandle(address otherAddress) public {
vm.assume(otherAddress != lensHandles.ownerOf(handleId));
vm.assume(otherAddress != hub.ownerOf(profileId));
vm.assume(otherAddress != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(otherAddress != proxyAdmin);
vm.prank(address(hub));
tokenHandleRegistry.migrationLink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.expectRevert(RegistryErrors.NotHandleNorTokenOwner.selector);
vm.prank(otherAddress);
tokenHandleRegistry.unlink(handleId, profileId);
}
function testResolve() public {
assertEq(tokenHandleRegistry.resolve(handleId), 0);
vm.prank(address(hub));
tokenHandleRegistry.migrationLink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
address newProfileHolder = makeAddr('NEW_PROFILE_HOLDER');
address newHandleHolder = makeAddr('NEW_HANDLE_HOLDER');
vm.prank(address(initialProfileHolder));
hub.transferFrom(initialProfileHolder, newProfileHolder, profileId);
vm.prank(address(initialHandleHolder));
lensHandles.transferFrom(initialHandleHolder, newHandleHolder, handleId);
// Still resolves after both tokens were moved to new owners.
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
vm.prank(address(newProfileHolder));
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
}
function testGetDefaultHandle() public {
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
vm.prank(address(hub));
tokenHandleRegistry.migrationLink(handleId, profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
address newProfileHolder = makeAddr('NEW_PROFILE_HOLDER');
address newHandleHolder = makeAddr('NEW_HANDLE_HOLDER');
vm.prank(address(initialProfileHolder));
hub.transferFrom(initialProfileHolder, newProfileHolder, profileId);
vm.prank(address(initialHandleHolder));
lensHandles.transferFrom(initialHandleHolder, newHandleHolder, handleId);
// Still gets default handle after both tokens were moved to new owners.
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.prank(address(newHandleHolder));
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testCannot_Resolve_IfHandleDoesNotExist(uint256 nonexistingHandleId) public {
vm.assume(!lensHandles.exists(nonexistingHandleId));
vm.expectRevert(RegistryErrors.DoesNotExist.selector);
tokenHandleRegistry.resolve(nonexistingHandleId);
}
function testCannot_GetDefaultHandle_IfProfileDoesNotExist(uint256 nonexistingProfileId) public {
vm.assume(!hub.exists(nonexistingProfileId));
vm.expectRevert(RegistryErrors.DoesNotExist.selector);
tokenHandleRegistry.getDefaultHandle(nonexistingProfileId);
}
function testResolve_IfBurnt() public {
vm.prank(address(hub));
tokenHandleRegistry.migrationLink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.prank(address(initialProfileHolder));
hub.burn(profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
vm.expectRevert(RegistryErrors.DoesNotExist.selector);
tokenHandleRegistry.getDefaultHandle(profileId);
}
function testGetDefaultHandle_IfBurnt() public {
vm.prank(address(hub));
tokenHandleRegistry.migrationLink(handleId, profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.prank(address(initialHandleHolder));
lensHandles.burn(handleId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testCannot_Unlink_IfNotLinked() public {
vm.expectRevert(RegistryErrors.NotLinked.selector);
vm.prank(initialProfileHolder);
tokenHandleRegistry.unlink(handleId, profileId);
}
function testFreshLink(address holder) public {
vm.assume(holder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(holder != proxyAdmin);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, holder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, holder, profileId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleLinked(handle, token, block.timestamp);
vm.prank(holder);
tokenHandleRegistry.link(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
}
function testLink_AfterHandleWasMoved(address firstHolder, address newHolder) public {
vm.assume(firstHolder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(firstHolder != proxyAdmin);
vm.assume(firstHolder != initialProfileHolder);
vm.assume(firstHolder != initialHandleHolder);
vm.assume(newHolder != address(0));
vm.assume(newHolder != proxyAdmin);
vm.assume(newHolder != initialProfileHolder);
vm.assume(newHolder != initialHandleHolder);
vm.assume(newHolder != firstHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, firstHolder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, firstHolder, profileId);
vm.prank(firstHolder);
tokenHandleRegistry.link(handleId, profileId);
vm.prank(firstHolder);
lensHandles.transferFrom(firstHolder, newHolder, handleId);
uint256 newProfileId = _createProfile(newHolder);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
RegistryTypes.Token memory newToken = RegistryTypes.Token({collection: address(hub), id: newProfileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleLinked(handle, newToken, block.timestamp);
vm.prank(newHolder);
tokenHandleRegistry.link(handleId, newProfileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
assertEq(tokenHandleRegistry.resolve(handleId), newProfileId);
assertEq(tokenHandleRegistry.getDefaultHandle(newProfileId), handleId);
}
function testLink_AfterProfileWasMoved(address firstHolder, address newHolder) public {
vm.assume(firstHolder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(firstHolder != proxyAdmin);
vm.assume(firstHolder != initialProfileHolder);
vm.assume(firstHolder != initialHandleHolder);
vm.assume(newHolder != address(0));
vm.assume(newHolder != proxyAdmin);
vm.assume(newHolder != initialProfileHolder);
vm.assume(newHolder != initialHandleHolder);
vm.assume(newHolder != firstHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, firstHolder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, firstHolder, profileId);
vm.prank(firstHolder);
tokenHandleRegistry.link(handleId, profileId);
vm.prank(firstHolder);
hub.transferFrom(firstHolder, newHolder, profileId);
uint256 newHandleId = lensHandles.mintHandle(newHolder, 'newhandle');
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
RegistryTypes.Handle memory newHandle = RegistryTypes.Handle({
collection: address(lensHandles),
id: newHandleId
});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleLinked(newHandle, token, block.timestamp);
vm.prank(newHolder);
tokenHandleRegistry.link(newHandleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
assertEq(tokenHandleRegistry.resolve(newHandleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), newHandleId);
}
function testLink_AfterBothProfileAndHandleWereMoved(address firstHolder, address newHolder) public {
address thirdHolder = makeAddr('THIRD_HOLDER');
vm.assume(firstHolder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(firstHolder != proxyAdmin);
vm.assume(firstHolder != initialProfileHolder);
vm.assume(firstHolder != initialHandleHolder);
vm.assume(firstHolder != thirdHolder);
vm.assume(newHolder != address(0));
vm.assume(newHolder != proxyAdmin);
vm.assume(newHolder != initialProfileHolder);
vm.assume(newHolder != initialHandleHolder);
vm.assume(newHolder != firstHolder);
vm.assume(newHolder != thirdHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, firstHolder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, firstHolder, profileId);
vm.prank(firstHolder);
tokenHandleRegistry.link(handleId, profileId);
vm.prank(firstHolder);
hub.transferFrom(firstHolder, newHolder, profileId);
uint256 newHandleId = lensHandles.mintHandle(thirdHolder, 'newhandle');
uint256 newProfileId = _createProfile(thirdHolder);
vm.prank(thirdHolder);
tokenHandleRegistry.link(newHandleId, newProfileId);
vm.prank(thirdHolder);
lensHandles.transferFrom(thirdHolder, newHolder, newHandleId);
RegistryTypes.Handle memory oldHandle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory oldToken = RegistryTypes.Token({collection: address(hub), id: profileId});
RegistryTypes.Handle memory newHandle = RegistryTypes.Handle({
collection: address(lensHandles),
id: newHandleId
});
RegistryTypes.Token memory newToken = RegistryTypes.Token({collection: address(hub), id: newProfileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(newHandle, newToken, block.timestamp);
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(oldHandle, oldToken, block.timestamp);
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleLinked(newHandle, oldToken, block.timestamp);
vm.prank(newHolder);
tokenHandleRegistry.link(newHandleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
assertEq(tokenHandleRegistry.getDefaultHandle(newProfileId), 0);
assertEq(tokenHandleRegistry.resolve(newHandleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), newHandleId);
}
function testUnlink(address holder) public {
vm.assume(holder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(holder != proxyAdmin);
vm.assume(holder != initialProfileHolder);
vm.assume(holder != initialHandleHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, holder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, holder, profileId);
vm.prank(holder);
tokenHandleRegistry.link(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
vm.prank(holder);
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testUnlink_ByProfileOwner_IfHandleWasMoved(address firstHolder, address newHolder) public {
vm.assume(firstHolder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(firstHolder != proxyAdmin);
vm.assume(firstHolder != initialProfileHolder);
vm.assume(firstHolder != initialHandleHolder);
vm.assume(newHolder != address(0));
vm.assume(newHolder != proxyAdmin);
vm.assume(newHolder != initialProfileHolder);
vm.assume(newHolder != initialHandleHolder);
vm.assume(newHolder != firstHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, firstHolder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, firstHolder, profileId);
vm.prank(firstHolder);
tokenHandleRegistry.link(handleId, profileId);
vm.prank(firstHolder);
lensHandles.transferFrom(firstHolder, newHolder, handleId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
vm.prank(firstHolder);
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testUnlink_ByNewHandleOwner_IfHandleWasMoved(address firstHolder, address newHolder) public {
vm.assume(firstHolder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(firstHolder != proxyAdmin);
vm.assume(firstHolder != initialProfileHolder);
vm.assume(firstHolder != initialHandleHolder);
vm.assume(newHolder != address(0));
vm.assume(newHolder != proxyAdmin);
vm.assume(newHolder != initialProfileHolder);
vm.assume(newHolder != initialHandleHolder);
vm.assume(newHolder != firstHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, firstHolder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, firstHolder, profileId);
vm.prank(firstHolder);
tokenHandleRegistry.link(handleId, profileId);
vm.prank(firstHolder);
lensHandles.transferFrom(firstHolder, newHolder, handleId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
vm.prank(newHolder);
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testUnlink_ByHandleOwner_IfProfileWasMoved(address firstHolder, address newHolder) public {
vm.assume(firstHolder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(firstHolder != proxyAdmin);
vm.assume(firstHolder != initialProfileHolder);
vm.assume(firstHolder != initialHandleHolder);
vm.assume(newHolder != address(0));
vm.assume(newHolder != proxyAdmin);
vm.assume(newHolder != initialProfileHolder);
vm.assume(newHolder != initialHandleHolder);
vm.assume(newHolder != firstHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, firstHolder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, firstHolder, profileId);
vm.prank(firstHolder);
tokenHandleRegistry.link(handleId, profileId);
vm.prank(firstHolder);
hub.transferFrom(firstHolder, newHolder, profileId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
vm.prank(firstHolder);
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testUnlink_ByNewProfileOwner_IfProfileWasMoved(address firstHolder, address newHolder) public {
vm.assume(firstHolder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(firstHolder != proxyAdmin);
vm.assume(firstHolder != initialProfileHolder);
vm.assume(firstHolder != initialHandleHolder);
vm.assume(newHolder != address(0));
vm.assume(newHolder != proxyAdmin);
vm.assume(newHolder != initialProfileHolder);
vm.assume(newHolder != initialHandleHolder);
vm.assume(newHolder != firstHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, firstHolder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, firstHolder, profileId);
vm.prank(firstHolder);
tokenHandleRegistry.link(handleId, profileId);
vm.prank(firstHolder);
hub.transferFrom(firstHolder, newHolder, profileId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
vm.prank(newHolder);
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testUnlink_IfHandleWasBurned(address holder) public {
vm.assume(holder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(holder != proxyAdmin);
vm.assume(holder != initialProfileHolder);
vm.assume(holder != initialHandleHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, holder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, holder, profileId);
vm.prank(holder);
tokenHandleRegistry.link(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.prank(holder);
lensHandles.burn(handleId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
vm.prank(holder);
tokenHandleRegistry.unlink(handleId, profileId);
vm.expectRevert(RegistryErrors.DoesNotExist.selector);
tokenHandleRegistry.resolve(handleId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testUnlink_IfProfileWasBurned(address holder) public {
vm.assume(holder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(holder != proxyAdmin);
vm.assume(holder != initialProfileHolder);
vm.assume(holder != initialHandleHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, holder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, holder, profileId);
vm.prank(holder);
tokenHandleRegistry.link(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.prank(holder);
hub.burn(profileId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
vm.prank(holder);
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
vm.expectRevert(RegistryErrors.DoesNotExist.selector);
tokenHandleRegistry.getDefaultHandle(profileId);
}
function testUnlink_IfHandleWasBurned_CalledByNotOwner(address holder) public {
vm.assume(holder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(holder != proxyAdmin);
vm.assume(holder != initialProfileHolder);
vm.assume(holder != initialHandleHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, holder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, holder, profileId);
vm.prank(holder);
tokenHandleRegistry.link(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.prank(holder);
lensHandles.burn(handleId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
address otherAddress = makeAddr('OTHER_ADDRESS');
vm.prank(otherAddress);
tokenHandleRegistry.unlink(handleId, profileId);
vm.expectRevert(RegistryErrors.DoesNotExist.selector);
tokenHandleRegistry.resolve(handleId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), 0);
}
function testUnlink_IfProfileWasBurned_CalledByNotOwner(address holder) public {
vm.assume(holder != address(0));
address proxyAdmin = address(uint160(uint256(vm.load(address(tokenHandleRegistry), ADMIN_SLOT))));
vm.assume(holder != proxyAdmin);
vm.assume(holder != initialProfileHolder);
vm.assume(holder != initialHandleHolder);
vm.prank(initialHandleHolder);
lensHandles.transferFrom(initialHandleHolder, holder, handleId);
vm.prank(initialProfileHolder);
hub.transferFrom(initialProfileHolder, holder, profileId);
vm.prank(holder);
tokenHandleRegistry.link(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), profileId);
assertEq(tokenHandleRegistry.getDefaultHandle(profileId), handleId);
vm.prank(holder);
hub.burn(profileId);
RegistryTypes.Handle memory handle = RegistryTypes.Handle({collection: address(lensHandles), id: handleId});
RegistryTypes.Token memory token = RegistryTypes.Token({collection: address(hub), id: profileId});
vm.expectEmit(true, true, true, true, address(tokenHandleRegistry));
emit RegistryEvents.HandleUnlinked(handle, token, block.timestamp);
address otherAddress = makeAddr('OTHER_ADDRESS');
vm.prank(otherAddress);
tokenHandleRegistry.unlink(handleId, profileId);
assertEq(tokenHandleRegistry.resolve(handleId), 0);
vm.expectRevert(RegistryErrors.DoesNotExist.selector);
tokenHandleRegistry.getDefaultHandle(profileId);
}
}