// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import 'test/base/BaseTest.t.sol'; import {MockDeprecatedCollectModule} from 'test/mocks/MockDeprecatedCollectModule.sol'; import {MockDeprecatedReferenceModule} from 'test/mocks/MockDeprecatedReferenceModule.sol'; /* This kind of tree is created: Post_1 | |-- Comment/Quote_0 -- Mirror_0 (mirror of a direct reference) | | | |-- Comment/Quote_1 -- Mirror_1 (mirror of a 1st level reference) | | | |-- Comment/Quote_2 -- Mirror_2 (mirror of a 2nd level reference) | | | |-- Comment/Quote_3 -- Mirror_3 (mirror of a 3rd level reference) | | |-- Comment/Quote_4 -- Mirror_4 (a different branch) | | |-- Mirror_5 (direct post mirror) */ /** * Tests shared among all operations where the Lens V2 Referral System applies, e.g. act, quote, comment, mirror. */ abstract contract ReferralSystemTest is BaseTest { uint256 testAccountId; address mockDeprecatedReferenceModule; address mockDeprecatedCollectModule; function _referralSystem_PrepareOperation( TestPublication memory target, uint256[] memory referrerProfileIds, uint256[] memory referrerPubIds ) internal virtual; // Returns true if expectRevert was added, so we avoid a dobule expectRevert scenario. function _referralSystem_ExpectRevertsIfNeeded( TestPublication memory target, uint256[] memory referrerProfileIds, uint256[] memory referrerPubIds ) internal virtual returns (bool); function _referralSystem_ExecutePreparedOperation() internal virtual; ///////////////////////////////// // Internal helpers ///////////////////////////////// function _referralSystem_PrepareOperation( TestPublication memory target, TestPublication memory referralPub ) private { _referralSystem_PrepareOperation( target, _toUint256Array(referralPub.profileId), _toUint256Array(referralPub.pubId) ); } // Returns true if expectRevert was added, so we avoid a dobule expectRevert scenario. function _referralSystem_ExpectRevertsIfNeeded( TestPublication memory target, TestPublication memory referralPub ) private returns (bool) { return _referralSystem_ExpectRevertsIfNeeded( target, _toUint256Array(referralPub.profileId), _toUint256Array(referralPub.pubId) ); } function _executeOperation(TestPublication memory target, TestPublication memory referralPub) private { _referralSystem_PrepareOperation(target, referralPub); _referralSystem_ExpectRevertsIfNeeded(target, referralPub); _referralSystem_ExecutePreparedOperation(); } ///////////////////////////////// // TODO: Move this to TestSetup? And get rid of this setUp function setUp() public virtual override { super.setUp(); mockDeprecatedReferenceModule = address(new MockDeprecatedReferenceModule()); mockDeprecatedCollectModule = address(new MockDeprecatedCollectModule(address(this))); } struct Tree { TestPublication post; TestPublication[] references; TestPublication[] mirrors; } function testV2UnverifiedReferrals() public virtual { TestAccount memory profileReferral = _loadAccountAs('PROFILE_REFERRAL'); TestPublication memory referralPub = TestPublication(profileReferral.profileId, 0); for (uint256 commentQuoteFuzzBitmap = 0; commentQuoteFuzzBitmap < 32; commentQuoteFuzzBitmap++) { Tree memory treeV2 = _createV2Tree(commentQuoteFuzzBitmap); { TestPublication memory target = treeV2.post; _executeOperation(target, referralPub); } { for (uint256 i = 0; i < treeV2.references.length; i++) { TestPublication memory target = treeV2.references[i]; _executeOperation(target, referralPub); } } } } function testCannot_PassV2UnverifiedReferral_SameAsTargetAuthor() public virtual { for (uint256 commentQuoteFuzzBitmap = 0; commentQuoteFuzzBitmap < 32; commentQuoteFuzzBitmap++) { Tree memory treeV2 = _createV2Tree(commentQuoteFuzzBitmap); { TestPublication memory target = treeV2.post; TestPublication memory referralPub = TestPublication(target.profileId, 0); _referralSystem_PrepareOperation(target, referralPub); if (!_referralSystem_ExpectRevertsIfNeeded(target, referralPub)) { vm.expectRevert(Errors.InvalidReferrer.selector); } _referralSystem_ExecutePreparedOperation(); } { for (uint256 i = 0; i < treeV2.references.length; i++) { TestPublication memory target = treeV2.references[i]; TestPublication memory referralPub = TestPublication(target.profileId, 0); _referralSystem_PrepareOperation(target, referralPub); if (!_referralSystem_ExpectRevertsIfNeeded(target, referralPub)) { vm.expectRevert(Errors.InvalidReferrer.selector); } _referralSystem_ExecutePreparedOperation(); } } } } function testV2Referrals() public virtual { for (uint256 commentQuoteFuzzBitmap = 0; commentQuoteFuzzBitmap < 32; commentQuoteFuzzBitmap++) { Tree memory treeV2 = _createV2Tree(commentQuoteFuzzBitmap); console.log('Created a tree. Executing operations...'); { // Target a post with quote/comment as referrals console.log('Target a post with quote/comment as referrals'); TestPublication memory target = treeV2.post; for (uint256 i = 0; i < treeV2.references.length; i++) { TestPublication memory referralPub = treeV2.references[i]; _executeOperation(target, referralPub); } } { // Target a post with mirrors as referrals console.log('Target a post with mirrors as referrals'); TestPublication memory target = treeV2.post; for (uint256 i = 0; i < treeV2.mirrors.length; i++) { TestPublication memory referralPub = treeV2.mirrors[i]; _executeOperation(target, referralPub); } } { // Target as a quote/comment node and pass another quote/comments as referral console.log('Target as a quote/comment node and pass another quote/comments as referral'); for (uint256 i = 0; i < treeV2.references.length; i++) { TestPublication memory target = treeV2.references[i]; for (uint256 j = 0; j < treeV2.references.length; j++) { TestPublication memory quoteOrCommentAsReferralPub = treeV2.references[j]; if (i == j) continue; // skip self // vm.expectCall /* */(); console.log('Target Quote/Comment: %s %s', target.profileId, target.pubId); console.log( 'Referral Quote/Comment: %s %s', quoteOrCommentAsReferralPub.profileId, quoteOrCommentAsReferralPub.pubId ); _executeOperation(target, quoteOrCommentAsReferralPub); } // One special case is a post as referal for reference node console.log('Special case: Target as a quote/comment node and pass post as referral'); TestPublication memory referralPub = treeV2.post; // vm.expectCall /* */(); _executeOperation(target, referralPub); } } { // Target as a quote/comment node and pass mirror as referral console.log('Target as a quote/comment node and pass mirror as referral'); for (uint256 i = 0; i < treeV2.references.length; i++) { TestPublication memory target = treeV2.references[i]; for (uint256 j = 0; j < treeV2.mirrors.length; j++) { TestPublication memory referralPub = treeV2.mirrors[j]; if (i == j) continue; // skip self // vm.expectCall /* */(); _executeOperation(target, referralPub); } } } } } function testV1_TargetPost_ReferralComment(uint256 v1FuzzBitmap) public virtual { vm.assume(v1FuzzBitmap < 2 ** 11); Tree memory treeV1 = _createV1Tree(v1FuzzBitmap); // Target a post with quote/comment as referrals TestPublication memory target = treeV1.post; for (uint256 i = 0; i < treeV1.references.length; i++) { TestPublication memory referralPub = treeV1.references[i]; _executeOperation(target, referralPub); } } function testV1_TargetPost_ReferralMirror(uint256 v1FuzzBitmap) public virtual { vm.assume(v1FuzzBitmap < 2 ** 11); Tree memory treeV1 = _createV1Tree(v1FuzzBitmap); // Target a post with mirrors as referrals TestPublication memory target = treeV1.post; for (uint256 i = 0; i < treeV1.mirrors.length; i++) { TestPublication memory referralPub = treeV1.mirrors[i]; _executeOperation(target, referralPub); } } function testV1_TargetComment_ReferralV1Post(uint256 v1FuzzBitmap) public virtual { vm.assume(v1FuzzBitmap < 2 ** 11); Tree memory treeV1 = _createV1Tree(v1FuzzBitmap); // Target comment with post as a referral TestPublication memory referralPub = treeV1.post; for (uint256 i = 0; i < treeV1.references.length; i++) { TestPublication memory target = treeV1.references[i]; console.log('Preparing operation...'); _referralSystem_PrepareOperation(target, referralPub); // Shoule revert as V1-contaminated trees don't have a root and only allow downwards referrals if (!_referralSystem_ExpectRevertsIfNeeded(target, referralPub)) { vm.expectRevert(Errors.InvalidReferrer.selector); } console.log('Trying to execute operation...'); _referralSystem_ExecutePreparedOperation(); } } function testV1_TargetComment_ReferralComment(uint256 v1FuzzBitmap) public virtual { vm.assume(v1FuzzBitmap < 2 ** 11); Tree memory treeV1 = _createV1Tree(v1FuzzBitmap); // Target as a comment node and pass another comments as referral for (uint256 i = 0; i < treeV1.references.length; i++) { TestPublication memory target = treeV1.references[i]; for (uint256 j = 1; j < treeV1.references.length; j++) { TestPublication memory referralPub = treeV1.references[j]; if (i == j) continue; // skip self _referralSystem_PrepareOperation(target, referralPub); Types.PublicationMemory memory targetPublication = hub.getPublication(target.profileId, target.pubId); Types.PublicationMemory memory referralPublication = hub.getPublication( referralPub.profileId, referralPub.pubId ); if ( !_referralSystem_ExpectRevertsIfNeeded(target, referralPub) && (_isV1LegacyPub(targetPublication) || referralPublication.pointedProfileId != target.profileId || referralPublication.pointedPubId != target.pubId) ) { // Non-pure V2 trees only allow one-level referrers. vm.expectRevert(Errors.InvalidReferrer.selector); } _referralSystem_ExecutePreparedOperation(); } } } function testV1_TargetComment_ReferralMirror(uint256 v1FuzzBitmap) public virtual { vm.assume(v1FuzzBitmap < 2 ** 11); Tree memory treeV1 = _createV1Tree(v1FuzzBitmap); // Target as a comment node and pass mirror as referral for (uint256 i = 0; i < treeV1.references.length; i++) { TestPublication memory target = treeV1.references[i]; for (uint256 j = 0; j < treeV1.mirrors.length; j++) { TestPublication memory referralPub = treeV1.mirrors[j]; if (i == j) continue; // skip self _referralSystem_PrepareOperation(target, referralPub); Types.PublicationMemory memory targetPublication = hub.getPublication(target.profileId, target.pubId); Types.PublicationMemory memory referralPublication = hub.getPublication( referralPub.profileId, referralPub.pubId ); if ( !_referralSystem_ExpectRevertsIfNeeded(target, referralPub) && (_isV1LegacyPub(targetPublication) || referralPublication.pointedProfileId != target.profileId || referralPublication.pointedPubId != target.pubId) ) { // Non-pure V2 trees only allow one-level referrers. vm.expectRevert(Errors.InvalidReferrer.selector); } _referralSystem_ExecutePreparedOperation(); } } } function _createV2Tree(uint256 commentQuoteFuzzBitmap) internal returns (Tree memory) { Tree memory tree; tree.references = new TestPublication[](5); tree.mirrors = new TestPublication[](6); tree.post = _post(); tree.references[0] = _commentOrQuote(tree.post, commentQuoteFuzzBitmap, 0); tree.mirrors[0] = _mirror(tree.references[0]); tree.references[1] = _commentOrQuote(tree.references[0], commentQuoteFuzzBitmap, 1); tree.mirrors[1] = _mirror(tree.references[1]); tree.references[2] = _commentOrQuote(tree.references[1], commentQuoteFuzzBitmap, 2); tree.mirrors[2] = _mirror(tree.references[2]); tree.references[3] = _commentOrQuote(tree.references[2], commentQuoteFuzzBitmap, 3); tree.mirrors[3] = _mirror(tree.references[3]); tree.references[4] = _commentOrQuote(tree.post, commentQuoteFuzzBitmap, 4); tree.mirrors[4] = _mirror(tree.references[4]); tree.mirrors[5] = _mirror(tree.post); return tree; } function _convertToV1(TestPublication memory pub, uint256 v1FuzzBitmap, uint256 v1FuzzBitmapIndex) internal { Types.PublicationMemory memory publication = hub.getPublication(pub.profileId, pub.pubId); Types.PublicationMemory memory pointedPub = hub.getPublication( publication.pointedProfileId, publication.pointedPubId ); if (_isV1LegacyPub(pointedPub)) { bool shouldConvertToV1 = ((v1FuzzBitmap >> (v1FuzzBitmapIndex)) & 1) != 0; if (shouldConvertToV1) { console.log( 'Converted (%s, %s) to V1 %s referenceModule', pub.profileId, pub.pubId, uint256(keccak256(abi.encodePacked(v1FuzzBitmap))) % 2 == 0 ? 'without' : 'with' ); _toLegacyV1Pub( pub.profileId, pub.pubId, uint256(keccak256(abi.encodePacked(v1FuzzBitmap))) % 2 == 0 ? address(0) : mockDeprecatedReferenceModule, publication.pubType == Types.PublicationType.Mirror ? address(0) : mockDeprecatedCollectModule ); } } } function _convertPostToV1(TestPublication memory pub) internal { console.log('Converted (%s, %s) to V1', pub.profileId, pub.pubId); if (pub.pubId % 2 == 0) { _toLegacyV1Pub(pub.profileId, pub.pubId, mockDeprecatedReferenceModule, mockDeprecatedCollectModule); } else { _toLegacyV1Pub(pub.profileId, pub.pubId, address(0), mockDeprecatedCollectModule); } } function _createV1Tree(uint256 v1FuzzBitmap) internal returns (Tree memory) { Tree memory tree; tree.references = new TestPublication[](5); tree.mirrors = new TestPublication[](6); tree.post = _post(); _convertPostToV1(tree.post); tree.references[0] = _comment(tree.post); tree.mirrors[0] = _mirror(tree.references[0]); tree.references[1] = _comment(tree.references[0]); tree.mirrors[1] = _mirror(tree.references[1]); tree.references[2] = _comment(tree.references[1]); tree.mirrors[2] = _mirror(tree.references[2]); tree.references[3] = _comment(tree.references[2]); tree.mirrors[3] = _mirror(tree.references[3]); tree.references[4] = _comment(tree.post); tree.mirrors[4] = _mirror(tree.references[4]); tree.mirrors[5] = _mirror(tree.post); _convertToV1(tree.references[0], v1FuzzBitmap, 0); _convertToV1(tree.mirrors[0], v1FuzzBitmap, 1); _convertToV1(tree.references[1], v1FuzzBitmap, 2); _convertToV1(tree.mirrors[1], v1FuzzBitmap, 3); _convertToV1(tree.references[2], v1FuzzBitmap, 4); _convertToV1(tree.mirrors[2], v1FuzzBitmap, 5); _convertToV1(tree.references[3], v1FuzzBitmap, 6); _convertToV1(tree.mirrors[3], v1FuzzBitmap, 7); _convertToV1(tree.references[4], v1FuzzBitmap, 8); _convertToV1(tree.mirrors[4], v1FuzzBitmap, 9); _convertToV1(tree.mirrors[5], v1FuzzBitmap, 10); return tree; } function _commentOrQuote( TestPublication memory testPub, uint256 commentQuoteFuzzBitmap, uint256 commentQuoteIndex ) internal returns (TestPublication memory) { uint256 commentQuoteFuzz = (commentQuoteFuzzBitmap >> (commentQuoteIndex)) & 1; if (commentQuoteFuzz == 0) { return _comment(testPub); } else { return _quote(testPub); } } function _post() internal returns (TestPublication memory) { testAccountId++; TestAccount memory publisher = _loadAccountAs(string.concat('TESTACCOUNT_', vm.toString(testAccountId))); Types.PostParams memory postParams = _getDefaultPostParams(); postParams.profileId = publisher.profileId; if (testAccountId % 2 == 0) { postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); } vm.prank(publisher.owner); uint256 pubId = hub.post(postParams); console.log('Created POST: %s, %s', publisher.profileId, pubId); return TestPublication(publisher.profileId, pubId); } function _mirror(TestPublication memory testPub) internal returns (TestPublication memory) { testAccountId++; TestAccount memory publisher = _loadAccountAs(string.concat('TESTACCOUNT_', vm.toString(testAccountId))); Types.MirrorParams memory mirrorParams = _getDefaultMirrorParams(); mirrorParams.profileId = publisher.profileId; mirrorParams.pointedPubId = testPub.pubId; mirrorParams.pointedProfileId = testPub.profileId; mirrorParams.referenceModuleData = abi.encode(true); vm.prank(publisher.owner); uint256 pubId = hub.mirror(mirrorParams); console.log( 'Created MIRROR: (%s) => (%s)', string.concat(vm.toString(publisher.profileId), ', ', vm.toString(pubId)), string.concat(vm.toString(testPub.profileId), ', ', vm.toString(testPub.pubId)) ); return TestPublication(publisher.profileId, pubId); } function _comment(TestPublication memory testPub) internal returns (TestPublication memory) { testAccountId++; TestAccount memory publisher = _loadAccountAs(string.concat('TESTACCOUNT_', vm.toString(testAccountId))); Types.CommentParams memory commentParams = _getDefaultCommentParams(); commentParams.profileId = publisher.profileId; commentParams.pointedPubId = testPub.pubId; commentParams.pointedProfileId = testPub.profileId; commentParams.referenceModuleData = abi.encode(true); if (testAccountId % 2 == 0) { commentParams.referenceModule = address(mockReferenceModule); commentParams.referenceModuleInitData = abi.encode(true); } vm.prank(publisher.owner); uint256 pubId = hub.comment(commentParams); console.log( 'Created COMMENT: (%s) => (%s)', string.concat(vm.toString(publisher.profileId), ', ', vm.toString(pubId)), string.concat(vm.toString(testPub.profileId), ', ', vm.toString(testPub.pubId)) ); return TestPublication(publisher.profileId, pubId); } function _quote(TestPublication memory testPub) internal returns (TestPublication memory) { testAccountId++; TestAccount memory publisher = _loadAccountAs(string.concat('TESTACCOUNT_', vm.toString(testAccountId))); Types.QuoteParams memory quoteParams = _getDefaultQuoteParams(); quoteParams.profileId = publisher.profileId; quoteParams.pointedPubId = testPub.pubId; quoteParams.pointedProfileId = testPub.profileId; quoteParams.referenceModuleData = abi.encode(true); if (testAccountId % 2 == 0) { quoteParams.referenceModule = address(mockReferenceModule); quoteParams.referenceModuleInitData = abi.encode(true); } vm.prank(publisher.owner); uint256 pubId = hub.quote(quoteParams); console.log( 'Created QUOTE: (%s) => (%s)', string.concat(vm.toString(publisher.profileId), ', ', vm.toString(pubId)), string.concat(vm.toString(testPub.profileId), ', ', vm.toString(testPub.pubId)) ); return TestPublication(publisher.profileId, pubId); } function testCannotExecuteOperationIf_ReferralProfileIdsPassedQty_DiffersFromPubIdsQty() public virtual { Types.PostParams memory postParams = _getDefaultPostParams(); postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); vm.prank(defaultAccount.owner); TestPublication memory targetPub = TestPublication(defaultAccount.profileId, hub.post(postParams)); TestPublication memory referralPub = _comment(targetPub); _referralSystem_PrepareOperation(targetPub, _toUint256Array(referralPub.profileId), _emptyUint256Array()); vm.expectRevert(Errors.ArrayMismatch.selector); _referralSystem_ExecutePreparedOperation(); _referralSystem_PrepareOperation(targetPub, _emptyUint256Array(), _toUint256Array(referralPub.pubId)); vm.expectRevert(Errors.ArrayMismatch.selector); _referralSystem_ExecutePreparedOperation(); } function testCannotPass_TargetedPublication_AsReferrer() public virtual { Types.PostParams memory postParams = _getDefaultPostParams(); postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); vm.prank(defaultAccount.owner); TestPublication memory targetPub = TestPublication(defaultAccount.profileId, hub.post(postParams)); _referralSystem_PrepareOperation(targetPub, targetPub); vm.expectRevert(Errors.InvalidReferrer.selector); _referralSystem_ExecutePreparedOperation(); } function testCannotPass_UnexistentProfile_AsReferrer(uint256 unexistentProfileId, uint8 pubId) public virtual { Types.PostParams memory postParams = _getDefaultPostParams(); postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); vm.prank(defaultAccount.owner); TestPublication memory targetPub = TestPublication(defaultAccount.profileId, hub.post(postParams)); vm.assume(!hub.exists(unexistentProfileId)); vm.assume(pubId != 0); _referralSystem_PrepareOperation(targetPub, _toUint256Array(unexistentProfileId), _toUint256Array(pubId)); vm.expectRevert(Errors.InvalidReferrer.selector); _referralSystem_ExecutePreparedOperation(); } function testCannotPass_UnexistentPublication_AsReferrer(uint256 unexistentPubId) public virtual { Types.PostParams memory postParams = _getDefaultPostParams(); postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); vm.prank(defaultAccount.owner); TestPublication memory targetPub = TestPublication(defaultAccount.profileId, hub.post(postParams)); TestPublication memory pub = _comment(targetPub); uint256 existentProfileId = pub.profileId; vm.assume(unexistentPubId > pub.pubId); _referralSystem_PrepareOperation( targetPub, _toUint256Array(existentProfileId), _toUint256Array(unexistentPubId) ); vm.expectRevert(Errors.InvalidReferrer.selector); _referralSystem_ExecutePreparedOperation(); } function testCannotPass_UnexistentProfile_AsUnverifiedReferrer(uint256 unexistentProfileId) public virtual { Types.PostParams memory postParams = _getDefaultPostParams(); postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); vm.prank(defaultAccount.owner); TestPublication memory targetPub = TestPublication(defaultAccount.profileId, hub.post(postParams)); vm.assume(!hub.exists(unexistentProfileId)); _referralSystem_PrepareOperation(targetPub, _toUint256Array(unexistentProfileId), _toUint256Array(0)); vm.expectRevert(Errors.InvalidReferrer.selector); _referralSystem_ExecutePreparedOperation(); } function testCannotPass_BurntProfile_AsUnverifiedReferrer() public virtual { Types.PostParams memory postParams = _getDefaultPostParams(); postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); vm.prank(defaultAccount.owner); TestPublication memory targetPub = TestPublication(defaultAccount.profileId, hub.post(postParams)); TestPublication memory referralPub = _comment(targetPub); address referralOwner = hub.ownerOf(referralPub.profileId); _effectivelyDisableProfileGuardian(referralOwner); vm.prank(referralOwner); hub.burn(referralPub.profileId); _referralSystem_PrepareOperation(targetPub, _toUint256Array(referralPub.profileId), _toUint256Array(0)); vm.expectRevert(Errors.InvalidReferrer.selector); _referralSystem_ExecutePreparedOperation(); } // This test might fail at some point when we check for duplicates! function testPassingDuplicatedReferralsIsAllowed() public { Types.PostParams memory postParams = _getDefaultPostParams(); postParams.referenceModule = address(mockReferenceModule); postParams.referenceModuleInitData = abi.encode(true); vm.prank(defaultAccount.owner); TestPublication memory targetPub = TestPublication(defaultAccount.profileId, hub.post(postParams)); TestPublication memory referralPub = _comment(targetPub); _referralSystem_PrepareOperation( targetPub, _toUint256Array(referralPub.profileId, referralPub.profileId), _toUint256Array(referralPub.pubId, referralPub.pubId) ); _referralSystem_ExecutePreparedOperation(); } }