mirror of
https://github.com/lens-protocol/core.git
synced 2026-01-10 06:38:05 -05:00
1826 lines
72 KiB
Solidity
1826 lines
72 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.13;
|
|
|
|
import 'test/base/BaseTest.t.sol';
|
|
import 'test/LensBaseERC721Test.t.sol';
|
|
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
|
import {IFollowNFT} from 'contracts/interfaces/IFollowNFT.sol';
|
|
import {FollowNFT} from 'contracts/FollowNFT.sol';
|
|
import {Types} from 'contracts/libraries/constants/Types.sol';
|
|
import {Base64} from 'solady/utils/Base64.sol';
|
|
import {LibString} from 'solady/utils/LibString.sol';
|
|
import {FollowTokenURI} from 'contracts/misc/token-uris/FollowTokenURI.sol';
|
|
import {IFollowTokenURI} from 'contracts/interfaces/IFollowTokenURI.sol';
|
|
|
|
contract FollowNFTTest is BaseTest, LensBaseERC721Test {
|
|
using stdJson for string;
|
|
using Strings for uint256;
|
|
|
|
uint256 constant MINT_NEW_TOKEN = 0;
|
|
address targetProfileOwner;
|
|
uint256 targetProfileId;
|
|
address followerProfileOwner;
|
|
uint256 followerProfileId;
|
|
address alreadyFollowingProfileOwner;
|
|
uint256 alreadyFollowingProfileId;
|
|
address targetFollowNFT;
|
|
uint256 lastAssignedTokenId;
|
|
address followHolder;
|
|
|
|
function setUp() public override {
|
|
super.setUp();
|
|
|
|
targetProfileOwner = address(0xC0FFEE);
|
|
targetProfileId = _createProfile(targetProfileOwner);
|
|
followerProfileOwner = address(this);
|
|
followerProfileId = _createProfile(followerProfileOwner);
|
|
|
|
followHolder = address(0xF0110111401DE2);
|
|
|
|
alreadyFollowingProfileOwner = address(0xF01108);
|
|
alreadyFollowingProfileId = _createProfile(alreadyFollowingProfileOwner);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
lastAssignedTokenId = hub.follow(
|
|
alreadyFollowingProfileId,
|
|
_toUint256Array(targetProfileId),
|
|
_toUint256Array(0),
|
|
_toBytesArray('')
|
|
)[0];
|
|
|
|
targetFollowNFT = hub.getProfile(targetProfileId).followNFT;
|
|
followNFT = FollowNFT(targetFollowNFT);
|
|
}
|
|
|
|
function _mintERC721(address to) internal virtual override returns (uint256) {
|
|
vm.assume(!_isLensHubProxyAdmin(to));
|
|
followerProfileId = _createProfile(to);
|
|
vm.prank(to);
|
|
uint256 tokenId = hub.follow(
|
|
followerProfileId,
|
|
_toUint256Array(targetProfileId),
|
|
_toUint256Array(0),
|
|
_toBytesArray('')
|
|
)[0];
|
|
|
|
vm.prank(to);
|
|
followNFT.wrap(tokenId);
|
|
return tokenId;
|
|
}
|
|
|
|
function _burnERC721(uint256 tokenId) internal virtual override {
|
|
return followNFT.burn(tokenId);
|
|
}
|
|
|
|
function _getERC721TokenAddress() internal view virtual override returns (address) {
|
|
return targetFollowNFT;
|
|
}
|
|
|
|
function testDoesNotSupportOtherThanTheExpectedInterfaces(uint32 interfaceId) public override {
|
|
vm.assume(bytes4(interfaceId) != bytes4(keccak256('royaltyInfo(uint256,uint256)')));
|
|
super.testDoesNotSupportOtherThanTheExpectedInterfaces(interfaceId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - General - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotCallFollowIfNotTheHub(address sender) public {
|
|
vm.assume(sender != address(hub));
|
|
vm.assume(sender != address(0));
|
|
|
|
vm.prank(sender);
|
|
|
|
vm.expectRevert(Errors.NotHub.selector);
|
|
followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
}
|
|
|
|
function testCannotFollowIfAlreadyFollowing() public {
|
|
vm.prank(address(hub));
|
|
|
|
vm.expectRevert(IFollowNFT.AlreadyFollowing.selector);
|
|
followNFT.follow({
|
|
followerProfileId: alreadyFollowingProfileId,
|
|
transactionExecutor: alreadyFollowingProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
}
|
|
|
|
function testCannotFollowWithTokenIfTheTokenDoesNotExist(uint256 unexistentTokenId) public {
|
|
vm.assume(unexistentTokenId != MINT_NEW_TOKEN);
|
|
vm.assume(followNFT.getFollowerProfileId(unexistentTokenId) == 0);
|
|
vm.assume(!followNFT.exists(unexistentTokenId));
|
|
vm.assume(followNFT.getProfileIdAllowedToRecover(unexistentTokenId) == 0);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
vm.expectRevert(IFollowNFT.FollowTokenDoesNotExist.selector);
|
|
|
|
followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: unexistentTokenId
|
|
});
|
|
}
|
|
|
|
function testCannotCallInitialize_AfterCreation(address anyAddress, uint256 profileId) public {
|
|
vm.expectRevert(Errors.Initialized.selector);
|
|
vm.prank(anyAddress);
|
|
followNFT.initialize(profileId);
|
|
}
|
|
|
|
function testOriginalAndGeneralFollowTimestamp(
|
|
uint32 initialTimestamp,
|
|
uint32 originalFollowTimestamp,
|
|
uint32 timeAdded
|
|
) public {
|
|
vm.assume(initialTimestamp != 0);
|
|
originalFollowTimestamp = uint32(bound(originalFollowTimestamp, initialTimestamp, type(uint32).max));
|
|
uint48 laterTimestamp = uint48(originalFollowTimestamp) + uint48(timeAdded);
|
|
|
|
// Initial non-zero timestamp
|
|
vm.warp(initialTimestamp);
|
|
|
|
uint256 expectedTokenId = lastAssignedTokenId + 1;
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(expectedTokenId), 0);
|
|
assertEq(followNFT.getFollowTimestamp(expectedTokenId), 0);
|
|
|
|
// Original follow timestamp
|
|
vm.warp(originalFollowTimestamp);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
uint256 assignedTokenId = hub.follow(
|
|
followerProfileId,
|
|
_toUint256Array(targetProfileId),
|
|
_toUint256Array(0),
|
|
_toBytesArray('')
|
|
)[0];
|
|
assertEq(assignedTokenId, expectedTokenId);
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(assignedTokenId), originalFollowTimestamp);
|
|
assertEq(followNFT.getFollowTimestamp(assignedTokenId), originalFollowTimestamp);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
followNFT.wrap(assignedTokenId);
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(assignedTokenId), originalFollowTimestamp);
|
|
assertEq(followNFT.getFollowTimestamp(assignedTokenId), originalFollowTimestamp);
|
|
|
|
hub.unfollow(followerProfileId, _toUint256Array(targetProfileId));
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(assignedTokenId), originalFollowTimestamp);
|
|
assertEq(followNFT.getFollowTimestamp(assignedTokenId), 0);
|
|
|
|
// Some later timestamp
|
|
vm.warp(laterTimestamp);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
uint256 repeatedAssignedTokenId = hub.follow(
|
|
followerProfileId,
|
|
_toUint256Array(targetProfileId),
|
|
_toUint256Array(assignedTokenId),
|
|
_toBytesArray('')
|
|
)[0];
|
|
|
|
assertEq(repeatedAssignedTokenId, assignedTokenId);
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(assignedTokenId), originalFollowTimestamp);
|
|
assertEq(followNFT.getFollowTimestamp(assignedTokenId), laterTimestamp);
|
|
}
|
|
|
|
function testFollowTimestampResetAfterFollowTokenIsBurned(uint32 initialTimestamp) public {
|
|
vm.assume(initialTimestamp != 0);
|
|
vm.warp(initialTimestamp);
|
|
|
|
uint256 expectedTokenId = lastAssignedTokenId + 1;
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(expectedTokenId), 0);
|
|
assertEq(followNFT.getFollowTimestamp(expectedTokenId), 0);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
uint256 assignedTokenId = hub.follow(
|
|
followerProfileId,
|
|
_toUint256Array(targetProfileId),
|
|
_toUint256Array(0),
|
|
_toBytesArray('')
|
|
)[0];
|
|
assertEq(assignedTokenId, expectedTokenId);
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(assignedTokenId), initialTimestamp);
|
|
assertEq(followNFT.getFollowTimestamp(assignedTokenId), initialTimestamp);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
followNFT.wrap(assignedTokenId);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
followNFT.burn(assignedTokenId);
|
|
|
|
console.log('followerProfileId', followNFT.getFollowData(assignedTokenId).followerProfileId);
|
|
console.log('originalFollowTimestamp', followNFT.getFollowData(assignedTokenId).originalFollowTimestamp);
|
|
console.log('followTimestamp', followNFT.getFollowData(assignedTokenId).followTimestamp);
|
|
console.log('profileIdAllowedToRecover', followNFT.getFollowData(assignedTokenId).profileIdAllowedToRecover);
|
|
|
|
assertEq(followNFT.getOriginalFollowTimestamp(assignedTokenId), initialTimestamp);
|
|
assertEq(followNFT.getFollowTimestamp(assignedTokenId), 0);
|
|
}
|
|
|
|
function testGetTokenURI_Fuzz() public {
|
|
IFollowTokenURI followTokenURIContract = new FollowTokenURI();
|
|
for (uint256 tokenId = type(uint256).max; tokenId > 0; tokenId >>= 16) {
|
|
for (
|
|
uint256 originalFollowTimestamp = type(uint48).max;
|
|
originalFollowTimestamp > 0;
|
|
originalFollowTimestamp >>= 8
|
|
) {
|
|
uint256 followedProfileId = type(uint256).max - tokenId;
|
|
string memory tokenURI = followTokenURIContract.getTokenURI(
|
|
tokenId,
|
|
followedProfileId,
|
|
originalFollowTimestamp
|
|
);
|
|
string memory base64prefix = 'data:application/json;base64,';
|
|
string memory decodedTokenURI = string(
|
|
Base64.decode(LibString.slice(tokenURI, bytes(base64prefix).length))
|
|
);
|
|
|
|
string memory tokenIdAsString = vm.toString(tokenId);
|
|
string memory followedProfileIdAsString = vm.toString(followedProfileId);
|
|
assertEq(decodedTokenURI.readString('.name'), string.concat('Follower #', tokenIdAsString));
|
|
assertEq(
|
|
decodedTokenURI.readString('.description'),
|
|
string.concat(
|
|
'Lens Protocol - Follower #',
|
|
tokenIdAsString,
|
|
' of Profile #',
|
|
followedProfileIdAsString
|
|
)
|
|
);
|
|
assertEq(decodedTokenURI.readUint('.attributes[0].value'), tokenId, "Token ID doesn't match");
|
|
assertEq(
|
|
decodedTokenURI.readUint('.attributes[1].value'),
|
|
bytes(tokenIdAsString).length,
|
|
"Token ID Digits doesn't match"
|
|
);
|
|
assertEq(
|
|
decodedTokenURI.readUint('.attributes[2].value'),
|
|
originalFollowTimestamp,
|
|
"Original Follow Timestamp doesn't match"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function testGetTokenURI() public {
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(lastAssignedTokenId);
|
|
|
|
uint256 tokenId = lastAssignedTokenId;
|
|
string memory tokenURI = followNFT.tokenURI(tokenId);
|
|
string memory base64prefix = 'data:application/json;base64,';
|
|
string memory decodedTokenURI = string(Base64.decode(LibString.slice(tokenURI, bytes(base64prefix).length)));
|
|
|
|
string memory tokenIdAsString = vm.toString(tokenId);
|
|
assertEq(decodedTokenURI.readString('.name'), string.concat('Follower #', tokenIdAsString));
|
|
assertEq(
|
|
decodedTokenURI.readString('.description'),
|
|
string.concat('Lens Protocol - Follower #', tokenIdAsString, ' of Profile #', vm.toString(targetProfileId))
|
|
);
|
|
assertEq(decodedTokenURI.readUint('.attributes[0].value'), tokenId, "Token ID doesn't match");
|
|
assertEq(
|
|
decodedTokenURI.readUint('.attributes[1].value'),
|
|
bytes(tokenIdAsString).length,
|
|
"Token ID Digits doesn't match"
|
|
);
|
|
assertEq(
|
|
decodedTokenURI.readUint('.attributes[2].value'),
|
|
followNFT.getOriginalFollowTimestamp(tokenId),
|
|
"Original Follow Timestamp doesn't match"
|
|
);
|
|
}
|
|
|
|
function testCannot_GetTokenURI_IfDoesNotExist_OrIsUnwrapped(uint256 tokenId) public {
|
|
vm.assume(!followNFT.exists(tokenId));
|
|
|
|
vm.expectRevert(Errors.TokenDoesNotExist.selector);
|
|
followNFT.tokenURI(tokenId);
|
|
}
|
|
|
|
function testSymbol(uint256 followedProfileId) public {
|
|
followNFT = FollowNFT(address(new TransparentUpgradeableProxy(hub.getFollowNFTImpl(), proxyAdmin, '')));
|
|
followNFT.initialize(followedProfileId);
|
|
|
|
string memory FOLLOW_NFT_SYMBOL_SUFFIX = '-Fl';
|
|
string memory expectedSymbol = string.concat(vm.toString(followedProfileId), FOLLOW_NFT_SYMBOL_SUFFIX);
|
|
assertEq(followNFT.symbol(), expectedSymbol);
|
|
}
|
|
|
|
// GetFollowerCount - we need to test all cases and see how it increases/decreases
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - Minting new token - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
// No negatives when minting a new token, all the failing cases will occur at LensHub level. See `FollowTest.t.sol`.
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - Minting new token - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
// Initial condition
|
|
function testFirstFollowTokenHasIdOne() public {
|
|
uint256 profileIdToFollow = _createProfile(address(this));
|
|
|
|
vm.prank(followerProfileOwner);
|
|
uint256 assignedTokenId = hub.follow(
|
|
followerProfileId,
|
|
_toUint256Array(profileIdToFollow),
|
|
_toUint256Array(0),
|
|
_toBytesArray('')
|
|
)[0];
|
|
|
|
assertEq(assignedTokenId, 1);
|
|
}
|
|
|
|
function testNewMintedTokenIdIsLastAssignedPlusOne() public {
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
|
|
assertEq(assignedTokenId, lastAssignedTokenId + 1);
|
|
}
|
|
|
|
function testFollowingMintingNewTokenSetsFollowerStatusCorrectly() public {
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
|
|
bool isFollowing = followNFT.isFollowing(followerProfileId);
|
|
assertEq(isFollowing, true);
|
|
|
|
uint256 followerProfileIdSet = followNFT.getFollowerProfileId(assignedTokenId);
|
|
assertEq(followerProfileIdSet, followerProfileId);
|
|
|
|
uint256 followIdByFollower = followNFT.getFollowTokenId(followerProfileId);
|
|
assertEq(followIdByFollower, assignedTokenId);
|
|
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore + 1);
|
|
}
|
|
|
|
function testExpectedFollowDataAfterMintingNewToken() public {
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
|
|
Types.FollowData memory followData = followNFT.getFollowData(assignedTokenId);
|
|
|
|
assertEq(followData.followerProfileId, followerProfileId);
|
|
assertEq(followData.originalFollowTimestamp, block.timestamp);
|
|
assertEq(followData.followTimestamp, block.timestamp);
|
|
assertEq(followData.profileIdAllowedToRecover, 0);
|
|
}
|
|
|
|
function testFollowTokenIsByDefaultUnwrapped() public {
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
|
|
vm.expectRevert(Errors.TokenDoesNotExist.selector);
|
|
followNFT.ownerOf(assignedTokenId);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - With unwrapped token - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannot_FollowWithUnwrappedTokenFromBurnedProfile_IfTheProfileWasNotBurned() public {
|
|
// Conditions to reach that state:
|
|
// - followerProfileId must not be following the target profile
|
|
assertFalse(followNFT.isFollowing(followerProfileId));
|
|
// - follow token must be used before and followTokenId != 0
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
assertTrue(followTokenId != 0);
|
|
// - token must be unwrapped (followTokenOwner == 0 and !exists)
|
|
assertFalse(followNFT.exists(followTokenId));
|
|
|
|
uint256 currentFollowerProfileId = followNFT.getFollowerProfileId(followTokenId);
|
|
// - currentFollowerProfileId should != 0
|
|
assertTrue(currentFollowerProfileId != 0);
|
|
// - CurrentFollowerProfileId Profile should exist
|
|
assertTrue(hub.exists(currentFollowerProfileId));
|
|
|
|
vm.prank(followerProfileOwner);
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
hub.follow(
|
|
followerProfileId,
|
|
_toUint256Array(targetProfileId),
|
|
_toUint256Array(followTokenId),
|
|
_toBytesArray('')
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - With unwrapped token - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testFollowWithUnwrappedTokenWhenCurrentFollowerWasBurnedAndTransactionExecutorIsFollowerOwner() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.burn(alreadyFollowingProfileId);
|
|
assertFalse(hub.exists(alreadyFollowingProfileId));
|
|
|
|
// NOTE: Follow NFT is not aware of Profile NFT burnings, follower count stays the same.
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore);
|
|
}
|
|
|
|
function testFollowWithUnwrappedTokenWhenCurrentFollowerWasBurnedAndTransactionExecutorIsApprovedDelegatee(
|
|
address executorAsApprovedDelegatee
|
|
) public {
|
|
vm.assume(executorAsApprovedDelegatee != followerProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.burn(alreadyFollowingProfileId);
|
|
assertFalse(hub.exists(alreadyFollowingProfileId));
|
|
|
|
// NOTE: Follow NFT is not aware of Profile NFT burnings, follower count stays the same.
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: executorAsApprovedDelegatee,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - With wrapped token - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannot_FollowWithWrappedToken_IfDoesNotHavePermissions(
|
|
address newFollowTokenHolder,
|
|
address delegatedExecutor
|
|
) public {
|
|
vm.assume(newFollowTokenHolder != followerProfileOwner);
|
|
vm.assume(newFollowTokenHolder != address(0));
|
|
vm.assume(delegatedExecutor != followerProfileOwner);
|
|
vm.assume(delegatedExecutor != address(0));
|
|
vm.assume(delegatedExecutor != newFollowTokenHolder);
|
|
|
|
vm.startPrank(followerProfileOwner);
|
|
uint256 followTokenId = hub.follow(
|
|
followerProfileId,
|
|
_toUint256Array(targetProfileId),
|
|
_toUint256Array(0),
|
|
_toBytesArray('')
|
|
)[0];
|
|
followNFT.wrap(followTokenId);
|
|
hub.unfollow(followerProfileId, _toUint256Array(targetProfileId));
|
|
followNFT.transferFrom(followerProfileOwner, newFollowTokenHolder, followTokenId);
|
|
hub.changeDelegatedExecutorsConfig(followerProfileId, _toAddressArray(delegatedExecutor), _toBoolArray(true));
|
|
vm.stopPrank();
|
|
|
|
// Requirements to reach the needed state:
|
|
// 1. Follower should not follow the target profile
|
|
assertFalse(followNFT.isFollowing(followerProfileId));
|
|
// 2. FollowTokenId != 0
|
|
assertTrue(followTokenId != 0);
|
|
// 3. FollowToken should be wrapped (have ownerOf != 0)
|
|
assertTrue(followNFT.exists(followTokenId));
|
|
address followTokenOwner = followNFT.ownerOf(followTokenId);
|
|
// 4. FollowApproval of followTokenId should != followerProfileId
|
|
assertTrue(followNFT.getFollowApproved(followTokenId) != followerProfileId);
|
|
// 5. FollowerProfileOwner should != followTokenOwner
|
|
assertTrue(followTokenOwner != followerProfileOwner);
|
|
// 6. TransactionExecutor should != followTokenOwner
|
|
assertTrue(followTokenOwner != delegatedExecutor);
|
|
// 7. FollowerProfileOwner should NOT be approvedForAll by followerTokenOwner
|
|
assertFalse(followNFT.isApprovedForAll(followTokenOwner, followerProfileOwner));
|
|
// 8. TransactionExecutor should NOT be approvedForAll by followerTokenOwner
|
|
assertFalse(followNFT.isApprovedForAll(followTokenOwner, delegatedExecutor));
|
|
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
vm.prank(address(hub));
|
|
followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
vm.prank(address(hub));
|
|
followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: delegatedExecutor,
|
|
followTokenId: followTokenId
|
|
});
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - With wrapped token - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testFollowWithWrappedTokenWhen_FollowerOwnerOwnsFollowTokenAndIsActingAsTransactionExecutor() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followerProfileOwner, followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
// NOTE: It just replaces the follower in the wrapped token, follower count stays the same.
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore);
|
|
}
|
|
|
|
function testFollowWithWrappedTokenWhen_FollowerOwnerOwnsFollowTokenAndIsActingAsTransactionExecutor_FollowerUnfollowFirst()
|
|
public
|
|
{
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore - 1);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followerProfileOwner, followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore);
|
|
}
|
|
|
|
function testFollowWithWrappedTokenWhen_FollowerOwnerAlsoOwnsFollowTokenAndTransactionExecutorIsApprovedDelegatee(
|
|
address executorAsApprovedDelegatee
|
|
) public {
|
|
vm.assume(executorAsApprovedDelegatee != followerProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followerProfileOwner, followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: executorAsApprovedDelegatee,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
}
|
|
|
|
function testFollowWithWrappedTokenWhen_ExecutorOwnsFollowTokenAndTransactionExecutorIsApprovedDelegatee(
|
|
address executorAsApprovedDelegatee
|
|
) public {
|
|
vm.assume(executorAsApprovedDelegatee != followerProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, executorAsApprovedDelegatee, followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: executorAsApprovedDelegatee,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
}
|
|
|
|
function testFollowWithWrappedTokenWhen_ExecutorIsApprovedForAllAndTransactionExecutorIsFollowerOwner() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.setApprovalForAll(followerProfileOwner, true);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
}
|
|
|
|
function testFollowWithWrappedTokenWhen_ExecutorIsApprovedForAllAndTransactionExecutorIsApprovedDelegatee(
|
|
address executorAsApprovedDelegatee
|
|
) public {
|
|
vm.assume(executorAsApprovedDelegatee != followerProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != alreadyFollowingProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.setApprovalForAll(executorAsApprovedDelegatee, true);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: executorAsApprovedDelegatee,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
}
|
|
|
|
function testFollowWithWrappedTokenWhen_ProfileIsApprovedToFollowAndTransactionExecutorIsFollowerOwner() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
assertEq(followNFT.getFollowApproved(followTokenId), followerProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
}
|
|
|
|
function testFollowWithWrappedTokenWhen_ProfileIsApprovedToFollowAndTransactionExecutorIsApprovedDelegatee(
|
|
address executorAsApprovedDelegatee
|
|
) public {
|
|
vm.assume(executorAsApprovedDelegatee != followerProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
assertEq(followNFT.getFollowApproved(followTokenId), followerProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: executorAsApprovedDelegatee,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(followerProfileId), followTokenId);
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Follow - Recovering token - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testFollowRecoveringToken() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
vm.prank(address(hub));
|
|
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore - 1);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: alreadyFollowingProfileId,
|
|
transactionExecutor: alreadyFollowingProfileOwner,
|
|
followTokenId: followTokenId
|
|
});
|
|
|
|
assertTrue(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(assignedTokenId, followTokenId);
|
|
assertEq(followNFT.getFollowTokenId(alreadyFollowingProfileId), followTokenId);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Unfollow - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotCallUnfollowIfNotTheHub(address sender) public {
|
|
vm.assume(sender != address(hub));
|
|
vm.assume(sender != address(0));
|
|
|
|
vm.prank(sender);
|
|
|
|
vm.expectRevert(Errors.NotHub.selector);
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
}
|
|
|
|
function testCannotUnfollowIfNotAlreadyFollowing() public {
|
|
assertFalse(followNFT.isFollowing(followerProfileId));
|
|
|
|
vm.prank(address(hub));
|
|
|
|
vm.expectRevert(IFollowNFT.NotFollowing.selector);
|
|
followNFT.unfollow({unfollowerProfileId: followerProfileId});
|
|
}
|
|
|
|
// TODO: Move to positives, because now it's possible to unfollow even if the token is wrapped and not owned.
|
|
function testCanUnfollowIfTokenIsWrappedAndUnfollowerOwnerOrTransactionExecutorDontHoldTheTokenOrApprovedForAll(
|
|
address unrelatedAddress
|
|
) public {
|
|
vm.assume(unrelatedAddress != address(0));
|
|
vm.assume(unrelatedAddress != alreadyFollowingProfileOwner);
|
|
vm.assume(!hub.isDelegatedExecutorApproved(alreadyFollowingProfileId, unrelatedAddress));
|
|
vm.assume(!followNFT.isApprovedForAll(alreadyFollowingProfileOwner, unrelatedAddress));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, unrelatedAddress, followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
}
|
|
|
|
function testCannotRemoveFollowerOnWrappedIfNotHolder(address unrelatedAddress) public {
|
|
vm.assume(unrelatedAddress != address(0));
|
|
vm.assume(unrelatedAddress != followHolder);
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followHolder, followTokenId);
|
|
|
|
vm.prank(unrelatedAddress);
|
|
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
followNFT.removeFollower({followTokenId: followTokenId});
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Unfollow - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testUnfollowAsFollowerProfileOwnerWhenTokenIsWrapped() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
vm.prank(address(hub));
|
|
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getFollowerProfileId(alreadyFollowingProfileId), 0);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore - 1);
|
|
}
|
|
|
|
function testUnfollowAsApprovedDelegatedExecutorOfFollowerOwnerWhenTokenIsWrapped(
|
|
address executorAsApprovedDelegatee
|
|
) public {
|
|
vm.assume(executorAsApprovedDelegatee != alreadyFollowingProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getFollowerProfileId(alreadyFollowingProfileId), 0);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
}
|
|
|
|
function testUnfollowAsFollowTokenOwnerWhenTokenIsWrapped(address followTokenOwner) public {
|
|
vm.assume(followTokenOwner != alreadyFollowingProfileOwner);
|
|
vm.assume(followTokenOwner != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followTokenOwner, followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getFollowerProfileId(alreadyFollowingProfileId), 0);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
}
|
|
|
|
function testUnfollowAsApprovedForAllByTokenOwnerWhenTokenIsWrapped(address approvedForAll) public {
|
|
vm.assume(approvedForAll != alreadyFollowingProfileOwner);
|
|
vm.assume(approvedForAll != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.setApprovalForAll(approvedForAll, true);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getFollowerProfileId(alreadyFollowingProfileId), 0);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
}
|
|
|
|
function testUnfollowAsFollowerProfileOwnerWhenTokenIsUnwrapped() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getFollowerProfileId(alreadyFollowingProfileId), 0);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
}
|
|
|
|
function testUnfollowAsApprovedDelegatedExecutorOfFollowerOwnerWhenTokenIsUnwrapped(
|
|
address executorAsApprovedDelegatee
|
|
) public {
|
|
vm.assume(executorAsApprovedDelegatee != alreadyFollowingProfileOwner);
|
|
vm.assume(executorAsApprovedDelegatee != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getFollowerProfileId(alreadyFollowingProfileId), 0);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
}
|
|
|
|
function testRemoveFollower() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
uint256 followerCountBefore = followNFT.getFollowerCount();
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followHolder, followTokenId);
|
|
|
|
vm.prank(followHolder);
|
|
followNFT.removeFollower({followTokenId: followTokenId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.getFollowerCount(), followerCountBefore - 1);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Wrap (tokenId) - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotWrapIfAlreadyWrapped() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
|
|
vm.expectRevert(IFollowNFT.AlreadyWrapped.selector);
|
|
followNFT.wrap(followTokenId);
|
|
}
|
|
|
|
function testCannotWrapIfTokenDoesNotExist(uint256 unexistentTokenId) public {
|
|
vm.assume(followNFT.getFollowerProfileId(unexistentTokenId) == 0);
|
|
vm.assume(!followNFT.exists(unexistentTokenId));
|
|
|
|
vm.expectRevert(IFollowNFT.FollowTokenDoesNotExist.selector);
|
|
followNFT.wrap(unexistentTokenId);
|
|
}
|
|
|
|
function testCannotWrapIfSenderIsNotFollowerOwner(address notFollowerOwner) public {
|
|
vm.assume(notFollowerOwner != alreadyFollowingProfileOwner);
|
|
vm.assume(notFollowerOwner != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(notFollowerOwner);
|
|
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
followNFT.wrap(followTokenId);
|
|
}
|
|
|
|
function testCannotWrapRecoveringWhenTheProfileAllowedToRecoverDoesNotExistAnymore() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.burn(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
vm.expectRevert(Errors.TokenDoesNotExist.selector);
|
|
followNFT.wrap(followTokenId);
|
|
}
|
|
|
|
function testCannotWrapRecoveringWhenTheSenderDoesNotOwnTheProfileAllowedToRecover(
|
|
address unrelatedAddress
|
|
) public {
|
|
vm.assume(unrelatedAddress != address(0));
|
|
vm.assume(unrelatedAddress != alreadyFollowingProfileOwner);
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.transferFrom({
|
|
from: alreadyFollowingProfileOwner,
|
|
to: unrelatedAddress,
|
|
tokenId: alreadyFollowingProfileId
|
|
});
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
followNFT.wrap(followTokenId);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Wrap (tokenId) - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testWrappedTokenOwnerIsFollowerProfileOwnerAfterUntyingAndWrapping() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
assertEq(followNFT.ownerOf(followTokenId), alreadyFollowingProfileOwner);
|
|
}
|
|
|
|
function testWrappedTokenStillHeldByPreviousFollowerOwnerAfterAFollowerProfileTransfer(
|
|
address newFollowerProfileOwner
|
|
) public {
|
|
vm.assume(newFollowerProfileOwner != followerProfileOwner);
|
|
vm.assume(newFollowerProfileOwner != address(0));
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
|
|
vm.prank(followerProfileOwner);
|
|
followNFT.wrap(assignedTokenId);
|
|
|
|
assertEq(followNFT.ownerOf(assignedTokenId), followerProfileOwner);
|
|
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
uint256 followerProfileIdSet = followNFT.getFollowerProfileId(assignedTokenId);
|
|
assertEq(followerProfileIdSet, followerProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(followerProfileOwner);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
hub.transferFrom(followerProfileOwner, newFollowerProfileOwner, followerProfileId);
|
|
|
|
assertEq(hub.ownerOf(followerProfileId), newFollowerProfileOwner);
|
|
assertEq(followNFT.ownerOf(assignedTokenId), followerProfileOwner);
|
|
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(followerProfileIdSet, followNFT.getFollowerProfileId(assignedTokenId));
|
|
}
|
|
|
|
function testRecoveringTokenThroughWrappingIt() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
assertEq(followNFT.ownerOf(followTokenId), alreadyFollowingProfileOwner);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
}
|
|
|
|
function testRecoveringTokenThroughWrappingItAfterProfileAllowedToRecoverWasTransferred(
|
|
address unrelatedAddress
|
|
) public {
|
|
vm.assume(unrelatedAddress != address(0));
|
|
vm.assume(unrelatedAddress != alreadyFollowingProfileOwner);
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.transferFrom({
|
|
from: alreadyFollowingProfileOwner,
|
|
to: unrelatedAddress,
|
|
tokenId: alreadyFollowingProfileId
|
|
});
|
|
|
|
vm.prank(unrelatedAddress);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
assertEq(followNFT.ownerOf(followTokenId), unrelatedAddress);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Wrap (tokenId, receiver) - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotWrapIfTokenReceiverIsAddressZero() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.expectRevert(Errors.InvalidParameter.selector);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId, address(0));
|
|
}
|
|
|
|
function testCannotWrapIfAlreadyWrapped(address receiver) public {
|
|
vm.assume(receiver != address(0));
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
|
|
vm.expectRevert(IFollowNFT.AlreadyWrapped.selector);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
}
|
|
|
|
function testCannotWrapIfTokenDoesNotExist(uint256 unexistentTokenId, address receiver) public {
|
|
vm.assume(receiver != address(0));
|
|
vm.assume(followNFT.getFollowerProfileId(unexistentTokenId) == 0);
|
|
vm.assume(!followNFT.exists(unexistentTokenId));
|
|
|
|
vm.expectRevert(IFollowNFT.FollowTokenDoesNotExist.selector);
|
|
followNFT.wrap(unexistentTokenId, receiver);
|
|
}
|
|
|
|
function testCannotWrapIfSenderIsNotFollowerOwner(address notFollowerOwner, address receiver) public {
|
|
vm.assume(receiver != address(0));
|
|
vm.assume(notFollowerOwner != alreadyFollowingProfileOwner);
|
|
vm.assume(notFollowerOwner != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(notFollowerOwner);
|
|
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
}
|
|
|
|
function testCannotWrapRecoveringWhenTheProfileAllowedToRecoverDoesNotExistAnymore(address receiver) public {
|
|
vm.assume(receiver != address(0));
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.burn(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
vm.expectRevert(Errors.TokenDoesNotExist.selector);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
}
|
|
|
|
function testCannotWrapRecoveringWhenTheSenderDoesNotOwnTheProfileAllowedToRecover(
|
|
address unrelatedAddress,
|
|
address receiver
|
|
) public {
|
|
vm.assume(receiver != address(0));
|
|
vm.assume(unrelatedAddress != address(0));
|
|
vm.assume(unrelatedAddress != alreadyFollowingProfileOwner);
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.transferFrom({
|
|
from: alreadyFollowingProfileOwner,
|
|
to: unrelatedAddress,
|
|
tokenId: alreadyFollowingProfileId
|
|
});
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Wrap (tokenId, receiver) - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testWrappedTokenOwnerIsReceiverProfileOwnerAfterUntyingAndWrapping(address receiver) public {
|
|
vm.assume(receiver != address(0));
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
|
|
assertEq(followNFT.ownerOf(followTokenId), receiver);
|
|
}
|
|
|
|
function testWrappedTokenStillHeldByPreviousFollowerOwnerAfterAFollowerProfileTransfer(
|
|
address receiver,
|
|
address newFollowerProfileOwner
|
|
) public {
|
|
vm.assume(receiver != address(0));
|
|
vm.assume(newFollowerProfileOwner != followerProfileOwner);
|
|
vm.assume(newFollowerProfileOwner != address(0));
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
|
|
vm.prank(followerProfileOwner);
|
|
followNFT.wrap(assignedTokenId, receiver);
|
|
|
|
assertEq(followNFT.ownerOf(assignedTokenId), receiver);
|
|
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
uint256 followerProfileIdSet = followNFT.getFollowerProfileId(assignedTokenId);
|
|
assertEq(followerProfileIdSet, followerProfileId);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
hub.transferFrom(followerProfileOwner, newFollowerProfileOwner, followerProfileId);
|
|
|
|
assertEq(hub.ownerOf(followerProfileId), newFollowerProfileOwner);
|
|
assertEq(followNFT.ownerOf(assignedTokenId), receiver);
|
|
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(followerProfileIdSet, followNFT.getFollowerProfileId(assignedTokenId));
|
|
}
|
|
|
|
function testRecoveringTokenThroughWrappingIt(address receiver) public {
|
|
vm.assume(receiver != address(0));
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
|
|
assertEq(followNFT.ownerOf(followTokenId), receiver);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
}
|
|
|
|
function testRecoveringTokenThroughWrappingItAfterProfileAllowedToRecoverWasTransferred(
|
|
address unrelatedAddress,
|
|
address receiver
|
|
) public {
|
|
vm.assume(receiver != address(0));
|
|
vm.assume(unrelatedAddress != address(0));
|
|
vm.assume(unrelatedAddress != alreadyFollowingProfileOwner);
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), alreadyFollowingProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
hub.transferFrom({
|
|
from: alreadyFollowingProfileOwner,
|
|
to: unrelatedAddress,
|
|
tokenId: alreadyFollowingProfileId
|
|
});
|
|
|
|
vm.prank(unrelatedAddress);
|
|
followNFT.wrap(followTokenId, receiver);
|
|
|
|
assertEq(followNFT.ownerOf(followTokenId), receiver);
|
|
assertEq(followNFT.getProfileIdAllowedToRecover(followTokenId), 0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Unwrap - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotUnwrapIfTokenDoesNotHaveAFollowerSet() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
vm.expectRevert(IFollowNFT.NotFollowing.selector);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.unwrap(followTokenId);
|
|
}
|
|
|
|
function testCannotUnwrapIfTokenIsAlreadyUnwrapped() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.expectRevert(Errors.TokenDoesNotExist.selector);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.unwrap(followTokenId);
|
|
}
|
|
|
|
function testCannotUnwrapIfSenderIsNotTokenOwnerOrApprovedOrApprovedForAll(address sender) public {
|
|
// You can't approve a token that is not wrapped, so no need to check for `followNFT.getApproved(followTokenId)`
|
|
vm.assume(sender != alreadyFollowingProfileOwner);
|
|
vm.assume(sender != address(0));
|
|
vm.assume(!followNFT.isApprovedForAll(alreadyFollowingProfileOwner, sender));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.expectRevert(Errors.NotOwnerOrApproved.selector);
|
|
vm.prank(sender);
|
|
followNFT.unwrap(followTokenId);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Unwrap - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testTokenOwnerCanUnwrapIt() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.unwrap(followTokenId);
|
|
|
|
assertFalse(followNFT.exists(followTokenId));
|
|
}
|
|
|
|
function testApprovedForAllCanUnwrapAToken(address approvedForAll) public {
|
|
vm.assume(approvedForAll != alreadyFollowingProfileOwner);
|
|
vm.assume(approvedForAll != address(0));
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.setApprovalForAll(approvedForAll, true);
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(approvedForAll);
|
|
followNFT.unwrap(followTokenId);
|
|
|
|
assertFalse(followNFT.exists(followTokenId));
|
|
}
|
|
|
|
function testApprovedForATokenCanUnwrapIt(address approved) public {
|
|
vm.assume(approved != alreadyFollowingProfileOwner);
|
|
vm.assume(approved != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approve(approved, followTokenId);
|
|
|
|
vm.prank(approved);
|
|
followNFT.unwrap(followTokenId);
|
|
|
|
assertFalse(followNFT.exists(followTokenId));
|
|
}
|
|
|
|
function testUnwrappedTokenStillTiedToFollowerProfileAfterAFollowerProfileTransfer(
|
|
address newFollowerProfileOwner
|
|
) public {
|
|
vm.assume(newFollowerProfileOwner != followerProfileOwner);
|
|
vm.assume(newFollowerProfileOwner != address(0));
|
|
|
|
vm.prank(address(hub));
|
|
|
|
uint256 assignedTokenId = followNFT.follow({
|
|
followerProfileId: followerProfileId,
|
|
transactionExecutor: followerProfileOwner,
|
|
followTokenId: MINT_NEW_TOKEN
|
|
});
|
|
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
uint256 followerProfileIdSet = followNFT.getFollowerProfileId(assignedTokenId);
|
|
assertEq(followerProfileIdSet, followerProfileId);
|
|
|
|
_effectivelyDisableProfileGuardian(followerProfileOwner);
|
|
|
|
vm.prank(followerProfileOwner);
|
|
hub.transferFrom(followerProfileOwner, newFollowerProfileOwner, followerProfileId);
|
|
|
|
assertEq(hub.ownerOf(followerProfileId), newFollowerProfileOwner);
|
|
|
|
assertTrue(followNFT.isFollowing(followerProfileId));
|
|
assertEq(followerProfileIdSet, followNFT.getFollowerProfileId(assignedTokenId));
|
|
|
|
vm.prank(newFollowerProfileOwner);
|
|
followNFT.wrap(assignedTokenId);
|
|
assertEq(followNFT.ownerOf(assignedTokenId), newFollowerProfileOwner);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Block - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotCallBlockIfNotTheHub(address sender) public {
|
|
vm.assume(sender != address(hub));
|
|
vm.assume(sender != address(0));
|
|
|
|
vm.prank(sender);
|
|
|
|
vm.expectRevert(Errors.NotHub.selector);
|
|
followNFT.processBlock(followerProfileId);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Block - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCanBlockSomeoneAlreadyBlocked() public {
|
|
vm.prank(address(hub));
|
|
followNFT.processBlock(followerProfileId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.processBlock(followerProfileId);
|
|
}
|
|
|
|
function testBlockingFollowerThatWasFollowingWithWrappedTokenMakesHimUnfollowButKeepsTheWrappedToken() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
assertTrue(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.processBlock(alreadyFollowingProfileId);
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
|
|
assertEq(followNFT.ownerOf(followTokenId), alreadyFollowingProfileOwner);
|
|
}
|
|
|
|
function testBlockingFollowerThatWasFollowingWithUnwrappedFirstWrapsTokenAndThenMakesHimUnfollowKeepingItWrapped()
|
|
public
|
|
{
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
assertFalse(followNFT.exists(followTokenId));
|
|
assertTrue(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.processBlock(alreadyFollowingProfileId);
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.ownerOf(followTokenId), alreadyFollowingProfileOwner);
|
|
}
|
|
|
|
function testBlockingProfileThatWasNotFollowingButItsOwnerHoldsWrappedFollowTokenDoesNotChangeAnything() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.unfollow({unfollowerProfileId: alreadyFollowingProfileId});
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.ownerOf(followTokenId), alreadyFollowingProfileOwner);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.processBlock(alreadyFollowingProfileId);
|
|
|
|
assertFalse(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.ownerOf(followTokenId), alreadyFollowingProfileOwner);
|
|
}
|
|
|
|
function testBlockingProfileThatWasNotFollowingButItsOwnerHoldsWrappedFollowTokenWithFollowerDoesNotChangeAnything()
|
|
public
|
|
{
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followerProfileOwner, followTokenId);
|
|
|
|
assertTrue(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.ownerOf(followTokenId), followerProfileOwner);
|
|
|
|
vm.prank(address(hub));
|
|
followNFT.processBlock(followerProfileId);
|
|
|
|
assertTrue(followNFT.isFollowing(alreadyFollowingProfileId));
|
|
assertEq(followNFT.ownerOf(followTokenId), followerProfileOwner);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Approve follow - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotApproveFollowForUnexistentProfile(uint256 unexistentProfileId) public {
|
|
vm.assume(!hub.exists(unexistentProfileId));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.expectRevert(Errors.TokenDoesNotExist.selector);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(unexistentProfileId, followTokenId);
|
|
}
|
|
|
|
function testCannotApproveFollowForUnexistentFollowToken(uint256 unexistentFollowTokenId) public {
|
|
vm.assume(unexistentFollowTokenId != 0);
|
|
vm.assume(!followNFT.exists(unexistentFollowTokenId));
|
|
vm.assume(followNFT.getFollowerProfileId(unexistentFollowTokenId) == 0);
|
|
|
|
vm.expectRevert(IFollowNFT.OnlyWrappedFollowTokens.selector);
|
|
followNFT.approveFollow(followerProfileId, unexistentFollowTokenId);
|
|
}
|
|
|
|
function testCannotApproveFollowForWrappedTokenIfCallerIsNotItsOwnerOrApprovedForAllByHim(address sender) public {
|
|
vm.assume(sender != alreadyFollowingProfileOwner);
|
|
vm.assume(sender != address(0));
|
|
vm.assume(!followNFT.isApprovedForAll(alreadyFollowingProfileOwner, sender));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.expectRevert(IFollowNFT.DoesNotHavePermissions.selector);
|
|
vm.prank(sender);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
}
|
|
|
|
function testCannotApproveFollowIfTokenIsUnwrapped() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.expectRevert(IFollowNFT.OnlyWrappedFollowTokens.selector);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Approve follow - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testApproveFollowWhenTokenIsWrappedAndCallerIsItsOwner() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), followerProfileId);
|
|
}
|
|
|
|
function testApproveFollowWhenTokenIsWrappedAndCallerIsApprovedForAllByItsOwner(address approvedForAll) public {
|
|
vm.assume(approvedForAll != alreadyFollowingProfileOwner);
|
|
vm.assume(approvedForAll != address(0));
|
|
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.setApprovalForAll(approvedForAll, true);
|
|
|
|
vm.prank(approvedForAll);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), followerProfileId);
|
|
}
|
|
|
|
function testFollowApprovalIsClearedAfterUnwrapping() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), followerProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.unwrap(followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
|
|
// Wraps again and checks that it keeps being clear.
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
}
|
|
|
|
function testFollowApprovalIsClearedAfterTransfer() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), followerProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.transferFrom(alreadyFollowingProfileOwner, followerProfileOwner, followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
|
|
// Transfers back to the previous owner and checks that it keeps being clear.
|
|
|
|
vm.prank(followerProfileOwner);
|
|
followNFT.transferFrom(followerProfileOwner, alreadyFollowingProfileOwner, followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
}
|
|
|
|
function testFollowApprovalIsClearedAfterBurning() public {
|
|
uint256 followTokenId = followNFT.getFollowTokenId(alreadyFollowingProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.wrap(followTokenId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.approveFollow(followerProfileId, followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), followerProfileId);
|
|
|
|
vm.prank(alreadyFollowingProfileOwner);
|
|
followNFT.burn(followTokenId);
|
|
|
|
assertEq(followNFT.getFollowApproved(followTokenId), 0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// ERC-2981 Royalties - Scenarios
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testSupportsErc2981Interface() public {
|
|
assertTrue(followNFT.supportsInterface(bytes4(keccak256('royaltyInfo(uint256,uint256)'))));
|
|
}
|
|
|
|
function testDefaultRoyaltiesAreSetTo10Percent(uint256 tokenId) public {
|
|
uint256 salePrice = 100;
|
|
uint256 expectedRoyalties = 10;
|
|
|
|
(address receiver, uint256 royalties) = followNFT.royaltyInfo(tokenId, salePrice);
|
|
|
|
assertEq(receiver, targetProfileOwner);
|
|
assertEq(royalties, expectedRoyalties);
|
|
}
|
|
|
|
function testSetRoyalties(uint256 royaltiesInBasisPoints, uint256 tokenId, uint256 salePrice) public {
|
|
uint256 basisPoints = 10000;
|
|
royaltiesInBasisPoints = bound(royaltiesInBasisPoints, 0, basisPoints);
|
|
uint256 salePriceTimesRoyalties;
|
|
unchecked {
|
|
salePriceTimesRoyalties = salePrice * royaltiesInBasisPoints;
|
|
// Fuzz prices that does not generate overflow, otherwise royaltyInfo will revert
|
|
vm.assume(salePrice == 0 || salePriceTimesRoyalties / salePrice == royaltiesInBasisPoints);
|
|
}
|
|
|
|
vm.prank(targetProfileOwner);
|
|
followNFT.setRoyalty(royaltiesInBasisPoints);
|
|
|
|
(address receiver, uint256 royalties) = followNFT.royaltyInfo(tokenId, salePrice);
|
|
|
|
assertEq(receiver, targetProfileOwner);
|
|
assertEq(royalties, salePriceTimesRoyalties / basisPoints);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// ERC-2981 Royalties - Negatives
|
|
//////////////////////////////////////////////////////////
|
|
|
|
function testCannotSetRoyaltiesIf_NotTargetProfileOwner(
|
|
address nonTargetProfileOwner,
|
|
uint256 royaltiesInBasisPoints
|
|
) public {
|
|
uint256 basisPoints = 10000;
|
|
royaltiesInBasisPoints = bound(royaltiesInBasisPoints, 0, basisPoints);
|
|
vm.assume(nonTargetProfileOwner != targetProfileOwner);
|
|
|
|
vm.prank(nonTargetProfileOwner);
|
|
vm.expectRevert(Errors.NotProfileOwner.selector);
|
|
followNFT.setRoyalty(royaltiesInBasisPoints);
|
|
}
|
|
|
|
function testCannotSetRoyaltiesIf_ExceedsBasisPoints(uint256 royaltiesInBasisPoints) public {
|
|
uint256 basisPoints = 10000;
|
|
vm.assume(royaltiesInBasisPoints > basisPoints);
|
|
|
|
vm.prank(targetProfileOwner);
|
|
vm.expectRevert(Errors.InvalidParameter.selector);
|
|
followNFT.setRoyalty(royaltiesInBasisPoints);
|
|
}
|
|
}
|