From 286bedcf0cb3ecb19b641aba79b9ccd548fadb5b Mon Sep 17 00:00:00 2001 From: Tina-57blocks <57793448+tyitang@users.noreply.github.com> Date: Mon, 16 Dec 2024 22:26:35 +0800 Subject: [PATCH] Add e2e tests for grouping module (#345) * test: Add e2e tests for grouping module * chore: Update group.royalty.test.ts --- .env.example | 1 + hardhat.config.ts | 3 +- .../e2e/grouping/group.authority.test.ts | 17 ++ test/hardhat/e2e/grouping/group.ipa.test.ts | 118 ++++++++- .../e2e/grouping/group.royalty.test.ts | 229 ++++++++++++++++++ test/hardhat/e2e/setup.ts | 7 +- test/hardhat/e2e/utils/erc20Helper.ts | 20 ++ 7 files changed, 391 insertions(+), 4 deletions(-) create mode 100644 test/hardhat/e2e/grouping/group.authority.test.ts create mode 100644 test/hardhat/e2e/grouping/group.royalty.test.ts diff --git a/.env.example b/.env.example index 932ed274..6576cd5c 100644 --- a/.env.example +++ b/.env.example @@ -19,4 +19,5 @@ DEVNET_URL = http:// DEVNET_CHAINID = 1315 DEVNET_PRIVATEKEY = DEVNET_USER1 = +DEVNET_USER2 = DEVNET_ERC721 = diff --git a/hardhat.config.ts b/hardhat.config.ts index 2bf71763..ad78b862 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -35,6 +35,7 @@ const DEVNET_URL = process.env.DEVNET_URL || "http://" const DEVNET_CHAINID = Number(process.env.DEVNET_CHAINID) || 1513 const DEVNET_PRIVATEKEY = process.env.DEVNET_PRIVATEKEY || "0xkey" const DEVNET_USER1 = process.env.DEVNET_USER1 || "0xkey" +const DEVNET_USER2 = process.env.DEVNET_USER2 || "0xkey" if (USE_TENDERLY) { tdly.setup({ @@ -71,7 +72,7 @@ const config: HardhatUserConfig = { odyssey: { chainId: DEVNET_CHAINID, url: DEVNET_URL, - accounts: [DEVNET_PRIVATEKEY, DEVNET_USER1], + accounts: [DEVNET_PRIVATEKEY, DEVNET_USER1, DEVNET_USER2], }, localhost: { chainId: 31337, diff --git a/test/hardhat/e2e/grouping/group.authority.test.ts b/test/hardhat/e2e/grouping/group.authority.test.ts new file mode 100644 index 00000000..11f19cae --- /dev/null +++ b/test/hardhat/e2e/grouping/group.authority.test.ts @@ -0,0 +1,17 @@ +// Test: Group Authorization + +import { EvenSplitGroupPool } from "../constants"; +import "../setup" +import { expect } from "chai" + +describe("Grouping Module Authorization", function () { + it("Non-admin whitelist group reward pool", async function () { + await expect( + this.groupingModule.connect(this.user1).whitelistGroupRewardPool(EvenSplitGroupPool, false) + ).to.be.rejectedWith(Error); + + const isWhitelisted = await this.ipAssetRegistry.isWhitelistedGroupRewardPool(EvenSplitGroupPool); + expect(isWhitelisted).to.be.true; + }); +}); + diff --git a/test/hardhat/e2e/grouping/group.ipa.test.ts b/test/hardhat/e2e/grouping/group.ipa.test.ts index 5916115e..5fcc2c83 100644 --- a/test/hardhat/e2e/grouping/group.ipa.test.ts +++ b/test/hardhat/e2e/grouping/group.ipa.test.ts @@ -106,7 +106,7 @@ describe("Add/Remove IP from Group IPA", function () { ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); await expect( this.groupingModule.connect(this.user1).addIp(groupId, [ipId]) - ).to.be.revertedWithCustomError(this.errors, "AccessController__PermissionDenied");; + ).to.be.revertedWithCustomError(this.errors, "AccessController__PermissionDenied"); const containsIp = await this.ipAssetRegistry.containsIp(groupId, ipId); expect(containsIp).to.be.false; @@ -147,3 +147,119 @@ describe("Add/Remove IP from Group IPA", function () { ).to.be.revertedWithCustomError(this.errors, "LicenseRegistry__IpHasNoGroupLicenseTerms"); }); }); + +describe("Group is locked due to registered derivative", function () { + let groupId: any; + let commRemixTermsId: any; + let ipId1: any; + let ipId2: any; + + before(async function () { + groupId = await expect( + this.groupingModule.registerGroup(EvenSplitGroupPool) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[5].args[0]); + console.log("groupId", groupId); + + commRemixTermsId = await registerPILTerms(true, 0, 10 * 10 ** 6, RoyaltyPolicyLRP); + await expect( + this.licensingModule.attachLicenseTerms(groupId, PILicenseTemplate, commRemixTermsId) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Register IP + console.log("============ Register IP1 ============"); + ({ ipId: ipId1 } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId, this.user1, this.user1)); + await expect( + this.licensingModule.connect(this.user1).setLicensingConfig(ipId1, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Add IP to the group + console.log("============ Add Ips to group ============"); + await expect( + this.groupingModule.addIp(groupId, [ipId1]) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + expect( + await this.evenSplitGroupPool.getTotalIps(groupId) + ).to.be.equal(1); + + // Register derivative IP + console.log("============ Register IP2 ============"); + ({ ipId: ipId2 } = await mintNFTAndRegisterIPA(this.user2, this.user2)); + await expect( + this.licensingModule.connect(this.user2).registerDerivative(ipId2, [groupId], [commRemixTermsId], PILicenseTemplate, "0x", 0, 100e6) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + }); + + it("Add Ip to locked group", async function () { + const { ipId } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId); + await expect( + this.licensingModule.setLicensingConfig(ipId, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + await expect( + this.groupingModule.addIp(groupId, [ipId]) + ).to.be.revertedWithCustomError(this.errors, "GroupingModule__GroupFrozenDueToHasDerivativeIps"); + }); + + it("Remove Ip from locked group", async function () { + await expect( + this.groupingModule.removeIp(groupId, [ipId1]) + ).to.be.revertedWithCustomError(this.errors, "GroupingModule__GroupFrozenDueToHasDerivativeIps"); + }); +}); + +describe("Group is locked due to minted license token", function () { + let groupId: any; + let commRemixTermsId: any; + let ipId1: any; + + before(async function () { + groupId = await expect( + this.groupingModule.registerGroup(EvenSplitGroupPool) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[5].args[0]); + console.log("groupId", groupId); + + commRemixTermsId = await registerPILTerms(true, 0, 10 * 10 ** 6, RoyaltyPolicyLRP); + await expect( + this.licensingModule.attachLicenseTerms(groupId, PILicenseTemplate, commRemixTermsId) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Register IP + console.log("============ Register IP1 ============"); + ({ ipId: ipId1 } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId, this.user1, this.user1)); + await expect( + this.licensingModule.connect(this.user1).setLicensingConfig(ipId1, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Add IP to the group + console.log("============ Add Ips to group ============"); + await expect( + this.groupingModule.addIp(groupId, [ipId1]) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + expect( + await this.evenSplitGroupPool.getTotalIps(groupId) + ).to.be.equal(1); + + // Mint license token + console.log("============ Group mint license token ============"); + await expect( + this.licensingModule.mintLicenseTokens(groupId, PILicenseTemplate, commRemixTermsId, 1, this.owner.address, "0x", 0) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + }); + + it("Add Ip to locked group", async function () { + const { ipId } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId); + await expect( + this.licensingModule.setLicensingConfig(ipId, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + await expect( + this.groupingModule.addIp(groupId, [ipId]) + ).to.be.revertedWithCustomError(this.errors, "GroupingModule__GroupFrozenDueToAlreadyMintLicenseTokens"); + }); + + it("Remove Ip from locked group", async function () { + await expect( + this.groupingModule.removeIp(groupId, [ipId1]) + ).to.be.revertedWithCustomError(this.errors, "GroupingModule__GroupFrozenDueToAlreadyMintLicenseTokens"); + }); +}); diff --git a/test/hardhat/e2e/grouping/group.royalty.test.ts b/test/hardhat/e2e/grouping/group.royalty.test.ts new file mode 100644 index 00000000..248e2579 --- /dev/null +++ b/test/hardhat/e2e/grouping/group.royalty.test.ts @@ -0,0 +1,229 @@ +// Test: Group IP Asset Royalty Distribution + +import "../setup" +import { expect } from "chai" +import { EvenSplitGroupPool, MockERC20, PILicenseTemplate, RoyaltyPolicyLAP } from "../constants" +import { LicensingConfig, registerPILTerms } from "../utils/licenseHelper"; +import { mintNFTAndRegisterIPA, mintNFTAndRegisterIPAWithLicenseTerms } from "../utils/mintNFTAndRegisterIPA"; +import { getErc20Balance } from "../utils/erc20Helper"; + +describe("Group IP Asset Royalty Distribution", function () { + let groupId: any; + let commRemixTermsId: any; + let ipId1: any; + let ipId2: any; + + let rewardPoolBalanceBefore: any; + let ip1BalanceBefore: any; + let ip2BalanceBefore: any; + + before(async function () { + console.log("============ Register Group ============"); + groupId = await expect( + this.groupingModule.registerGroup(EvenSplitGroupPool) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[5].args[0]); + console.log("groupId", groupId); + + console.log("============ Register Lisence Terms ============"); + commRemixTermsId = await registerPILTerms(true, 0, 10 * 10 ** 6, RoyaltyPolicyLAP); + await expect( + this.licensingModule.attachLicenseTerms(groupId, PILicenseTemplate, commRemixTermsId) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Register IP + console.log("============ Register IP1 ============"); + ({ ipId: ipId1 } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId, this.user1, this.user1)); + await expect( + this.licensingModule.connect(this.user1).setLicensingConfig(ipId1, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + console.log("============ Register IP2 ============"); + ({ ipId: ipId2 } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId, this.user2, this.user2)); + await expect( + this.licensingModule.connect(this.user2).setLicensingConfig(ipId2, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Add IP to the group + console.log("============ Add IPs to group ============"); + await expect( + this.groupingModule.addIp(groupId, [ipId1, ipId2]) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + expect( + await this.evenSplitGroupPool.getTotalIps(groupId) + ).to.be.equal(2); + }); + + it("Group royalties even split by member IPs", async function () { + // Register drivative IP + console.log("============ Register Derivative IP3 ============"); + const {ipId: ipId3} = await mintNFTAndRegisterIPA(); + await expect( + this.licensingModule.registerDerivative(ipId3, [groupId], [commRemixTermsId], PILicenseTemplate, "0x", 0, 100e6) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + rewardPoolBalanceBefore = await getErc20Balance(EvenSplitGroupPool); + + // Pay royalty to IP3 + console.log("============ Pay rayalty to IP3 ============"); + await expect( + this.royaltyModule.payRoyaltyOnBehalf(ipId3, ipId3, MockERC20, 1000) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // console.log("============ Transfer to vault ============"); + await expect( + this.royaltyPolicyLAP.transferToVault(ipId3, groupId, MockERC20) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Collect royalty + console.log("============ Collect royalty ============"); + await expect( + this.groupingModule.collectRoyalties(groupId, MockERC20) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Check reward pool balance after royalty collection + expect( + await getErc20Balance(EvenSplitGroupPool) + ).to.be.equal(rewardPoolBalanceBefore + 100n); + + // Get claimable + console.log("============ Get claimable ============"); + const claimableIp1 = await expect( + this.groupingModule.connect(this.user1).getClaimableReward(groupId, MockERC20, [ipId1]) + ).not.to.be.rejectedWith(Error); + console.log("IP1 claimable", claimableIp1); + const claimableIp2 = await expect( + this.groupingModule.connect(this.user2).getClaimableReward(groupId, MockERC20, [ipId2]) + ).not.to.be.rejectedWith(Error); + console.log("IP2 claimable", claimableIp2); + + // Mint license token to trigger vault creation + console.log("============ Mint license token ============"); + await expect( + this.licensingModule.mintLicenseTokens(ipId1, PILicenseTemplate, commRemixTermsId, 1, this.owner.address, "0x", 0) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + await expect( + this.licensingModule.mintLicenseTokens(ipId2, PILicenseTemplate, commRemixTermsId, 1, this.owner.address, "0x", 0) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + const vaultIp1 = await this.royaltyModule.ipRoyaltyVaults(ipId1); + console.log("vaultIp1", vaultIp1); + const vaultIp2 = await this.royaltyModule.ipRoyaltyVaults(ipId2); + console.log("vaultIp2", vaultIp2); + + ip1BalanceBefore = await getErc20Balance(vaultIp1); + ip2BalanceBefore = await getErc20Balance(vaultIp2); + + console.log("============ IP1 claim rewards ============"); + await expect( + this.groupingModule.connect(this.user1).claimReward(groupId, MockERC20, [ipId1]) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + console.log("============ IP2 claim rewards ============"); + await expect( + this.groupingModule.connect(this.user2).claimReward(groupId, MockERC20, [ipId2]) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + console.log("============ Check the balance after claim rewards ============"); + const rewardPoolBalance = await getErc20Balance(EvenSplitGroupPool); + const ip1Balance = await getErc20Balance(vaultIp1); + const ip2Balance = await getErc20Balance(vaultIp2); + expect(rewardPoolBalance).to.be.equal(rewardPoolBalanceBefore); + expect(ip1Balance).to.be.equal(ip1BalanceBefore + 50n); + expect(ip2Balance).to.be.equal(ip2BalanceBefore + 50n); + }); +}); + +describe("Non-Owner/Member Claim Group Royalty", function () { + let groupId: any; + let commRemixTermsId: any; + let ipId1: any; + let ipId2: any; + + before(async function () { + console.log("============ Register Group ============"); + groupId = await expect( + this.groupingModule.registerGroup(EvenSplitGroupPool) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[5].args[0]); + console.log("groupId", groupId); + + console.log("============ Register Lisence Terms ============"); + commRemixTermsId = await registerPILTerms(true, 0, 10 * 10 ** 6, RoyaltyPolicyLAP); + await expect( + this.licensingModule.attachLicenseTerms(groupId, PILicenseTemplate, commRemixTermsId) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Register IP + console.log("============ Register IP1 ============"); + ({ ipId: ipId1 } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId)); + await expect( + this.licensingModule.setLicensingConfig(ipId1, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + console.log("============ Register IP2 ============"); + ({ ipId: ipId2 } = await mintNFTAndRegisterIPAWithLicenseTerms(commRemixTermsId, this.user2, this.user2)); + await expect( + this.licensingModule.connect(this.user2).setLicensingConfig(ipId2, PILicenseTemplate, commRemixTermsId, LicensingConfig) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Add IP to the group + console.log("============ Add IPs to group ============"); + await expect( + this.groupingModule.addIp(groupId, [ipId1, ipId2]) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + expect( + await this.evenSplitGroupPool.getTotalIps(groupId) + ).to.be.equal(2); + + // Mint license token + console.log("============ Mint license token ============"); + await expect( + this.licensingModule.mintLicenseTokens(groupId, PILicenseTemplate, commRemixTermsId, 1, this.owner.address, "0x", 0) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + await expect( + this.licensingModule.mintLicenseTokens(ipId1, PILicenseTemplate, commRemixTermsId, 1, this.owner.address, "0x", 0) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + await expect( + this.licensingModule.mintLicenseTokens(ipId2, PILicenseTemplate, commRemixTermsId, 1, this.owner.address, "0x", 0) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + }); + + it("Non-Owner/Member collects group royalties", async function () { + // Pay royalty + console.log("============ Pay royalty to group ============"); + await expect( + this.royaltyModule.payRoyaltyOnBehalf(groupId, this.owner.address, MockERC20, 100) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + const rewardPoolBalanceBefore = await getErc20Balance(EvenSplitGroupPool); + + console.log("============ Collect royalty ============"); + await expect( + this.groupingModule.connect(this.user1).collectRoyalties(groupId, MockERC20) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + + // Check reward pool balance after royalty collection + expect( + await getErc20Balance(EvenSplitGroupPool) + ).to.be.equal(rewardPoolBalanceBefore + 100n); + + const vaultIp1 = await this.royaltyModule.ipRoyaltyVaults(ipId1); + const vaultIp2 = await this.royaltyModule.ipRoyaltyVaults(ipId2); + const ip1BalanceBefore = await getErc20Balance(vaultIp1); + const ip2BalanceBefore = await getErc20Balance(vaultIp2); + + console.log("============ Claim rewards ============"); + await expect( + this.groupingModule.connect(this.user1).claimReward(groupId, MockERC20, [ipId1, ipId2]) + ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); + expect( + await getErc20Balance(EvenSplitGroupPool) + ).to.be.equal(rewardPoolBalanceBefore); + expect( + await getErc20Balance(vaultIp1) + ).to.be.equal(ip1BalanceBefore + 50n); + expect( + await getErc20Balance(vaultIp2) + ).to.be.equal(ip2BalanceBefore + 50n); + }); +}); diff --git a/test/hardhat/e2e/setup.ts b/test/hardhat/e2e/setup.ts index 91bee71e..dfc47411 100644 --- a/test/hardhat/e2e/setup.ts +++ b/test/hardhat/e2e/setup.ts @@ -2,7 +2,7 @@ import hre from "hardhat"; import { network } from "hardhat"; -import { GroupingModule, IPAssetRegistry, LicenseRegistry, LicenseToken, LicensingModule, PILicenseTemplate, RoyaltyPolicyLAP, MockERC20, RoyaltyPolicyLRP, AccessController, RoyaltyModule } from "./constants"; +import { GroupingModule, IPAssetRegistry, LicenseRegistry, LicenseToken, LicensingModule, PILicenseTemplate, RoyaltyPolicyLAP, MockERC20, RoyaltyPolicyLRP, AccessController, RoyaltyModule, EvenSplitGroupPool } from "./constants"; import { terms } from "./licenseTermsTemplate"; import { approveSpender, checkAndApproveSpender, getAllowance, mintAmount } from "./utils/erc20Helper"; import { check } from "prettier"; @@ -16,10 +16,13 @@ before(async function () { this.groupingModule = await hre.ethers.getContractAt("GroupingModule", GroupingModule); this.licenseTemplate = await hre.ethers.getContractAt("PILicenseTemplate", PILicenseTemplate); this.accessController = await hre.ethers.getContractAt("AccessController", AccessController); + this.royaltyModule = await hre.ethers.getContractAt("RoyaltyModule", RoyaltyModule); + this.evenSplitGroupPool = await hre.ethers.getContractAt("EvenSplitGroupPool", EvenSplitGroupPool); + this.royaltyPolicyLAP = await hre.ethers.getContractAt("RoyaltyPolicyLAP", RoyaltyPolicyLAP); this.errors = await hre.ethers.getContractFactory("Errors"); console.log(`================= Load Users =================`); - [this.owner, this.user1] = await hre.ethers.getSigners(); + [this.owner, this.user1, this.user2] = await hre.ethers.getSigners(); console.log(`================= Chain ID =================`); const networkConfig = network.config; diff --git a/test/hardhat/e2e/utils/erc20Helper.ts b/test/hardhat/e2e/utils/erc20Helper.ts index 1126d0d9..35782c39 100644 --- a/test/hardhat/e2e/utils/erc20Helper.ts +++ b/test/hardhat/e2e/utils/erc20Helper.ts @@ -104,3 +104,23 @@ export async function checkAndApproveSpender(owner: any, spender: any, amount: b await approveSpender(spender, amount, owner); } }; + +export async function getErc20Balance(address: string): Promise { + console.log("============ Get Erc20 Balance ============"); + const contractAbi = [ + // Read-Only Functions + "function balanceOf(address owner) view returns (uint256)", + "function decimals() view returns (uint8)", + "function symbol() view returns (string)", + + // Authenticated Functions + "function transfer(address to, uint amount) returns (bool)", + + // Events + "event Transfer(address indexed from, address indexed to, uint amount)", + ]; + const contract = await hre.ethers.getContractAt(contractAbi, MockERC20); + const balance = await contract.balanceOf(address); + console.log(address, balance); + return balance; +};