Skip to content

Commit

Permalink
Setup e2e tests (storyprotocol#325)
Browse files Browse the repository at this point in the history
* setup e2e

* update e2e struct

* add IP Assets tests

* update test struct

* add ipa test

* add tests for register license terms

* add tests for register license terms

* add common methods in setup

* update common methods

* add test for attach license terms

* add tests for mint license tokens

* add permission test

* update permission test

* update var name

* update common and ipa tests

* update test config

* Update .env.example

* Create README.md

* Add file level comment and fix review comment

* Update README.md

* Update README.md

---------

Co-authored-by: Jacqueline Zhang <[email protected]>
Co-authored-by: jia57b <[email protected]>
  • Loading branch information
3 people authored Dec 4, 2024
1 parent 622a862 commit e03d3f7
Show file tree
Hide file tree
Showing 15 changed files with 644 additions and 64 deletions.
9 changes: 8 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ TENDERLY_URL =
TENDERLY_PRIVATEKEY =

# ETHERSCAN APIY KEY
ETHERSCAN_API_KEY = ETHERSCANAPIKEYETHERSCANAPIKEY
ETHERSCAN_API_KEY = ETHERSCANAPIKEYETHERSCANAPIKEY

# TESTNET
DEVNET_URL = http://
DEVNET_CHAINID = 1315
DEVNET_PRIVATEKEY =
DEVNET_USER1 =
DEVNET_ERC721 =
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ typechain
# converage
lcov.info

# test
mochawesome-report

16 changes: 14 additions & 2 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { HardhatConfig, HardhatUserConfig } from "hardhat/types"
import "hardhat-contract-sizer" // npx hardhat size-contracts
import "solidity-coverage"
import "solidity-docgen"
import "@nomicfoundation/hardhat-chai-matchers"

require("dotenv").config()

Expand All @@ -30,6 +31,11 @@ const USE_TENDERLY = process.env.USE_TENDERLY === "true"
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || "key"
const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY || "key"

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"

if (USE_TENDERLY) {
tdly.setup({
automaticVerifications: true,
Expand All @@ -41,7 +47,7 @@ const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: "0.8.23",
version: "0.8.26",
},
],
settings: {
Expand All @@ -62,6 +68,11 @@ const config: HardhatUserConfig = {
hardhat: {
chainId: 31337,
},
odyssey: {
chainId: DEVNET_CHAINID,
url: DEVNET_URL,
accounts: [DEVNET_PRIVATEKEY, DEVNET_USER1],
},
localhost: {
chainId: 31337,
url: "http://127.0.0.1:8545/",
Expand Down Expand Up @@ -101,7 +112,8 @@ const config: HardhatUserConfig = {
coinmarketcap: COINMARKETCAP_API_KEY,
},
mocha: {
timeout: 20_000,
timeout: 60_000,
reporter: "mochawesome",
},
etherscan: {
apiKey: ETHERSCAN_API_KEY,
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@
"docgen": "hardhat docgen"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@nomicfoundation/hardhat-foundry": "^1.1.1",
"@nomicfoundation/hardhat-verify": "^2.0.3",
"@openzeppelin/hardhat-upgrades": "^3.0.2",
"@tenderly/hardhat-tenderly": "^2.2.1",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "4",
"@types/mocha": "^10.0.10",
"@types/node": "^22.10.0",
"base64-sol": "^1.1.0",
"chai": "^5.0.3",
"chai": "4",
"dotenv": "^16.4.1",
"ds-test": "https://github.com/dapphub/ds-test",
"eslint": "^8.56.0",
Expand All @@ -45,6 +49,7 @@
"husky": "^8.0.0",
"minimatch": "^9.0.3",
"mocha": "^10.2.0",
"mochawesome": "^7.1.3",
"prettier": "^3.0.0",
"prettier-plugin-solidity": "^1.1.0",
"solhint": "^4.1.1",
Expand All @@ -53,7 +58,8 @@
"solidity-coverage": "^0.8.6",
"solidity-docgen": "^0.6.0-beta.36",
"ts-node": "^10.9.2",
"typechain": "^8.3.2"
"typechain": "^8.3.2",
"typescript": "^5.7.2"
},
"dependencies": {
"@openzeppelin/contracts": "5.0.2",
Expand Down
32 changes: 32 additions & 0 deletions test/hardhat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Story Protocol End-to-End Testing

This folder contains story protocol end-to-end test scripts.

## Requirements

Please install the following:

- [Foundry / Foundryup](https://github.com/gakonst/foundry)
- [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started#overview)

## Quickstart

Install the dependencies: run yarn command at project root. If you encounter any issues, try to remove node-modules and yarn.lock then run yarn again.

```sh
yarn # this installs packages
```

You'll need to add the variables refer to the .env.example to a .env file at project root.

Then, at project root run the tests with command:

```sh
npx hardhat test --network odyssey
```

You can specify the file path if you want to run test on a specific file:

```sh
npx hardhat test [file-path] --network odyssey
```
28 changes: 28 additions & 0 deletions test/hardhat/e2e/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// This is the deployed protocol address constants file.

export const AccessController = "0xf709c8001E94e2ca6F98b7fFBCd5BD3943E46D81";
export const CoreMetadataModule = "0x89630Ccf23277417FBdfd3076C702F5248267e78";
export const CoreMetadataViewModule = "0x6839De4A647eE2311bd765f615E09f7bd930ed25";
export const DisputeModule = "0x692B47fa72eE7Ac0Ec617ea384a0cAD41098F712";
export const EvenSplitGroupPool = "0xC384B56fD62d6679Cd62A2fE0dA3fe4560f33391";
export const GroupNFT = "0x5d7C6e71290f034bED4C241eD78642204ad1178A";
export const GroupingModule = "0xa731948cfE05135ad77d48C71f75066333Da78Bf";
export const IPAccountImpl = "0x24F08796561d6E1AC08e82b68BF4d9500B374Af6";
export const IPAssetRegistry = "0x28E59E91C0467e89fd0f0438D47Ca839cDfEc095";
export const IPGraphACL = "0x680E66e4c7Df9133a7AFC1ed091089B32b89C4ae";
export const IpRoyaltyVaultBeacon = "0x1F9CEDe79f5Af0a0A8E527Ad84e6C74f57D5F926";
export const IpRoyaltyVaultImpl = "0x1081250219B16cc3903Aa2d2d1403A75c6A2F9f5";
export const LicenseRegistry = "0xBda3992c49E98392e75E78d82B934F3598bA495f";
export const LicenseToken = "0xB138aEd64814F2845554f9DBB116491a077eEB2D";
export const LicensingModule = "0x5a7D9Fa17DE09350F481A53B470D798c1c1aabae";
export const MockERC20 = "0x12A8b0DcC6e3bB0915638361D9D49942Da07F455";
export const ModuleRegistry = "0x9F18c5723BC4Ee447CF9B01a8543D3b08b7F09C7";
export const PILicenseTemplate = "0x58E2c909D557Cd23EF90D14f8fd21667A5Ae7a93";
export const ProtocolAccessManager = "0xD22ff1C7e88aF45166aEFe000C4C0c4873Afa284";
export const ProtocolPauseAdmin = "0x65C6Ec6Cc074eaf7ba3970C540b4379C9BcA8A67";
export const RoyaltyModule = "0xEa6eD700b11DfF703665CCAF55887ca56134Ae3B";
export const RoyaltyPolicyLAP = "0x28b4F70ffE5ba7A26aEF979226f77Eb57fb9Fdb6";
export const RoyaltyPolicyLRP = "0x7D2d9af4E4ab14Afcfd86436BC348928B40963Dd";

// Mock ERC721 contract address
export const MockERC721 = process.env.DEVNET_ERC721 as string;
24 changes: 24 additions & 0 deletions test/hardhat/e2e/grouping/group.ipa.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Test: Group IP Asset

import "../setup"
import { expect } from "chai"
import { EvenSplitGroupPool } from "../constants"

describe("Group IPA", function () {
it("Register Group IPA with whitelisted group pool", async function () {

const 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)
expect(groupId).to.be.properHex(40);
});

it("Register Group IPA with non-whitelisted group pool", async function () {
const nonWhitelistedGroupPool = "0xC384B56fD62d6679Cd62A2fE0dA3fe4560f33300"
await expect(
this.groupingModule.registerGroup(nonWhitelistedGroupPool)
).to.be.rejectedWith(Error)
});
});
65 changes: 65 additions & 0 deletions test/hardhat/e2e/ipa/ipa.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Test: IP Asset

import "../setup"
import { expect } from "chai"
import { mintNFT } from "../utils/nftHelper"
import hre from "hardhat";
import { MockERC721 } from "../constants";

describe("IP Asset", function () {
let signers:any;

this.beforeAll("Get Signers", async function () {
// Get the signers
signers = await hre.ethers.getSigners();
})

it("NFT owner register IP Asset with an NFT token", async function () {
const tokenId = await mintNFT(signers[0]);
const connectedRegistry = this.ipAssetRegistry.connect(signers[0]);

const ipId = await expect(
connectedRegistry.register(this.chainId, MockERC721, tokenId)
).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]);
console.log("ipId:", ipId);

expect(ipId).to.not.be.empty.and.to.be.a("HexString");

const isRegistered = await expect(
connectedRegistry.isRegistered(ipId)
).not.to.be.rejectedWith(Error);

expect(isRegistered).to.equal(true);
});

it("Non-NFT owner register IP asset with an NFT token", async function () {
const tokenId = await mintNFT(signers[0]);
const connectedRegistry = this.ipAssetRegistry.connect(signers[1]);

const ipId = await expect(
connectedRegistry.register(this.chainId, MockERC721, tokenId)
).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]);
console.log("ipId:", ipId);

expect(ipId).to.not.be.empty.and.to.be.a("HexString");

const isRegistered = await expect(
connectedRegistry.isRegistered(ipId)
).not.to.be.rejectedWith(Error);

expect(isRegistered).to.equal(true);
});

it("Register IP asset, the caller doesn’t have enough IP token", async function () {
const tokenId = await mintNFT(signers[0]);

// generate random wallet
const randomWallet = hre.ethers.Wallet.createRandom();
const randomSigner = randomWallet.connect(hre.ethers.provider);
const connectedRegistry = this.ipAssetRegistry.connect(randomSigner);

await expect(
connectedRegistry.register(this.chainId, MockERC721, tokenId)
).to.be.rejectedWith(`insufficient funds`, `"code": -32000, "message": "insufficient funds for gas * price + value: balance 0`);
});
});
23 changes: 23 additions & 0 deletions test/hardhat/e2e/licenseTermsTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// This file is used to define the terms of a license that will be used in the tests.

import hre from "hardhat";

export const terms = {
transferable: true,
royaltyPolicy: hre.ethers.ZeroAddress,
defaultMintingFee: 0,
expiration: 0,
commercialUse: false,
commercialAttribution: false,
commercializerChecker: hre.ethers.ZeroAddress,
commercializerCheckerData: hre.ethers.ZeroAddress,
commercialRevShare: 0,
commercialRevCeiling: 0,
derivativesAllowed: true,
derivativesAttribution: false,
derivativesApproval: false,
derivativesReciprocal: false,
derivativeRevCeiling: 0,
currency: hre.ethers.ZeroAddress,
uri: "",
};
56 changes: 56 additions & 0 deletions test/hardhat/e2e/permission/permission.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Test: Permission

import "../setup"
import { expect } from "chai"
import { mintNFT } from "../utils/nftHelper"
import hre from "hardhat";
import { LicensingModule, MockERC721 } from "../constants";

describe("Permission", function () {
let signers:any;

this.beforeAll("Get Signers", async function () {
// Get the signers
signers = await hre.ethers.getSigners();
console.log("signers:", signers[0].address);
})

it("Add a new ALLOW permission of IP asset for an signer and change the permission to DENY", async function () {
const tokenId = await mintNFT(signers[0].address);
const connectedRegistry = this.ipAssetRegistry.connect(signers[0]);
const func = hre.ethers.encodeBytes32String("attachLicenseTerms").slice(0, 10);
const ALLOW_permission = 1;
const DENY_permission = 2;
let permissionAfter: number;
let result: any;

const ipId = await expect(
connectedRegistry.register(this.chainId, MockERC721, tokenId)
).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]);
console.log("ipId:", ipId);
expect(ipId).to.not.be.empty.and.to.be.a("HexString");

const connecedAccessController = this.accessController.connect(signers[0]);

const permissionBefore = await connecedAccessController.getPermission(ipId, signers[0].address, LicensingModule, func);
expect(permissionBefore).to.equal(0);

// add ALLOW permission
result = await connecedAccessController.setPermission(ipId, signers[0].address, LicensingModule, func, ALLOW_permission);
expect(result.hash).to.not.be.empty.and.to.be.a("HexString");
await result.wait();

// check the permission
permissionAfter = await connecedAccessController.getPermission(ipId, signers[0].address, LicensingModule, func);
expect(permissionAfter).to.equal(ALLOW_permission);

// Change to DENY permission
result = await connecedAccessController.setPermission(ipId, signers[0].address, LicensingModule, func, DENY_permission);
expect(result.hash).to.not.be.empty.and.to.be.a("HexString");
await result.wait();

// check the permission
permissionAfter = await connecedAccessController.getPermission(ipId, signers[0].address, LicensingModule, func);
expect(permissionAfter).to.equal(DENY_permission);
});
});
51 changes: 51 additions & 0 deletions test/hardhat/e2e/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This file is a root hook used to setup preconditions before running the tests.

import hre from "hardhat";
import { network } from "hardhat";
import { GroupingModule, IPAssetRegistry, LicenseRegistry, LicenseToken, LicensingModule, PILicenseTemplate, RoyaltyPolicyLAP, MockERC20, RoyaltyPolicyLRP, AccessController } from "./constants";
import { terms } from "./licenseTermsTemplate";

before(async function () {
console.log(`================= Load Contract =================`);
this.ipAssetRegistry = await hre.ethers.getContractAt("IPAssetRegistry", IPAssetRegistry);
this.licenseRegistry = await hre.ethers.getContractAt("LicenseRegistry", LicenseRegistry);
this.licenseToken = await hre.ethers.getContractAt("LicenseToken", LicenseToken);
this.licensingModule = await hre.ethers.getContractAt("LicensingModule", LicensingModule);
this.groupingModule = await hre.ethers.getContractAt("GroupingModule", GroupingModule);
this.licenseTemplate = await hre.ethers.getContractAt("PILicenseTemplate", PILicenseTemplate);
this.accessController = await hre.ethers.getContractAt("AccessController", AccessController);

console.log(`================= Load Users =================`);
[this.owner, this.user1] = await hre.ethers.getSigners();

console.log(`================= Chain ID =================`);
const networkConfig = network.config;
this.chainId = networkConfig.chainId;
console.log("chainId: ", this.chainId);

console.log(`================= Register non-commercial PIL license terms =================`);
await this.licenseTemplate.registerLicenseTerms(terms);
this.nonCommericialLicenseId = await this.licenseTemplate.getLicenseTermsId(terms);
console.log("Non-commercial licenseTermsId: ", this.nonCommericialLicenseId);

console.log(`================= Register commercial-use PIL license terms =================`);
let testTerms = terms;
testTerms.royaltyPolicy = RoyaltyPolicyLAP;
testTerms.defaultMintingFee = 30;
testTerms.commercialUse = true;
testTerms.currency = MockERC20;
await this.licenseTemplate.registerLicenseTerms(testTerms);
this.commericialUseLicenseId = await this.licenseTemplate.getLicenseTermsId(testTerms);
console.log("Commercial-use licenseTermsId: ", this.commericialUseLicenseId);

console.log(`================= Register commercial-remix PIL license terms =================`);
testTerms = terms;
testTerms.royaltyPolicy = RoyaltyPolicyLRP;
testTerms.defaultMintingFee = 80;
testTerms.commercialUse = true;
testTerms.commercialRevShare = 100;
testTerms.currency = MockERC20;
await this.licenseTemplate.registerLicenseTerms(testTerms);
this.commericialRemixLicenseId = await this.licenseTemplate.getLicenseTermsId(testTerms);
console.log("Commercial-remix licenseTermsId: ", this.commericialRemixLicenseId);
});
Loading

0 comments on commit e03d3f7

Please sign in to comment.