Files
core/test/ReferralSystem.t.sol
2023-06-26 17:40:57 +01:00

639 lines
27 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import 'test/mocks/MockModule.sol';
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(new MockDeprecatedReferenceModule());
address mockDeprecatedCollectModule = address(new 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();
}
/////////////////////////////////
function setUp() public virtual override {
super.setUp();
}
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);
{
// 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
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
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 /* */();
_executeOperation(target, quoteOrCommentAsReferralPub);
}
// One special case is a post as referal for reference node
TestPublication memory referralPub = treeV2.post;
// vm.expectCall /* */();
_executeOperation(target, referralPub);
}
}
{
// 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];
_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);
}
_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.Publication memory targetPublication = hub.getPublication(target.profileId, target.pubId);
Types.Publication 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.Publication memory targetPublication = hub.getPublication(target.profileId, target.pubId);
Types.Publication 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.Publication memory publication = hub.getPublication(pub.profileId, pub.pubId);
Types.Publication 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);
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();
}
}