From 3a25b850ee795ce968297f9cf11fd81d17ffd38b Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sat, 20 Jan 2024 16:30:17 -0800 Subject: [PATCH] Very Basic Integration Test (#11) * feat: test integration & mod commits * fix: remove deleted file * fix: integration workflow * feat: unit test coverages * fix: nit file * tmp: royalty & distributor --- .github/workflows/test.yml | 2 +- .gitignore | 3 + Makefile | 10 +- contracts/IPAccountImpl.sol | 2 +- contracts/interfaces/IAccessController.sol | 2 +- contracts/interfaces/IIPAccount.sol | 2 +- .../interfaces/licensing/IParamVerifier.sol | 3 + contracts/interfaces/modules/base/IModule.sol | 2 +- .../registries/IIPAccountRegistry.sol | 2 +- .../interfaces/registries/IModuleRegistry.sol | 2 +- contracts/registries/IPAccountRegistry.sol | 2 +- foundry.toml | 6 + lcov.info | 589 +++++++++++++++++- lib/forge-std/src/console.sol | 2 +- lib/forge-std/src/console2.sol | 2 +- test/foundry/AccessController.t.sol | 232 +++---- test/foundry/DisputeModule.t.sol | 50 +- test/foundry/IPAccount.t.sol | 36 +- test/foundry/IPAccount.tree | 23 + test/foundry/IPRecordRegistry.t.sol | 4 +- test/foundry/LicenseRegistry.t.sol | 20 +- test/foundry/ModuleRegistry.t.sol | 68 +- test/foundry/integration/Integration.t.sol | 458 ++++++++++++++ .../EmergenceUniverse.t.sol | 26 + .../flows/emergence-universe/README.md | 10 + test/foundry/mocks/MockAccessController.sol | 2 +- test/foundry/mocks/MockERC20.sol | 16 + test/foundry/mocks/MockERC721.sol | 26 +- test/foundry/mocks/MockModule.sol | 2 +- test/foundry/mocks/MockUSDC.sol | 20 + .../{ => registries}/IPAccountRegistry.t.sol | 41 +- .../resolvers/IPMetadataResolver.t.sol | 2 +- test/foundry/utils/Users.sol | 36 ++ test/utils/DeployHelper.sol | 24 +- 34 files changed, 1512 insertions(+), 215 deletions(-) create mode 100644 test/foundry/IPAccount.tree create mode 100644 test/foundry/integration/Integration.t.sol create mode 100644 test/foundry/integration/flows/emergence-universe/EmergenceUniverse.t.sol create mode 100644 test/foundry/integration/flows/emergence-universe/README.md create mode 100644 test/foundry/mocks/MockERC20.sol create mode 100644 test/foundry/mocks/MockUSDC.sol rename test/foundry/{ => registries}/IPAccountRegistry.t.sol (64%) create mode 100644 test/foundry/utils/Users.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c4784747c..6cecbc92f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,4 +66,4 @@ jobs: # - name: Code Coverage # run: # forge coverage --report lcov --report summary - # id: forge-code-coverage + # id: forge-code-coverage \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8c6e3db0f..7624885d0 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ node_modules/ .vscode .DS_Store +pnpm-lock.yaml + +coverage diff --git a/Makefile b/Makefile index 07cb84709..c370266fb 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ -include .env -.PHONY: all test clean +.PHONY: all test clean coverage all: clean install build # Clean the repo forge-clean :; forge clean -clean :; npx hardhat clean +clean :; npx hardhat clean # Remove modules forge-remove :; rm -rf .gitmodules && rm -rf .git/modules/* && rm -rf lib && touch .gitmodules && git add . && git commit -m "modules" @@ -27,6 +27,12 @@ slither :; slither ./contracts format :; npx prettier --write contracts/**/*.sol && npx prettier --write contracts/*.sol +coverage: + mkdir -p coverage + forge coverage --report lcov --fork-url https://rpc.ankr.com/eth --fork-block-number 19042069 + lcov --remove lcov.info -o lcov.info 'test/*' + genhtml lcov.info --output-dir coverage + # solhint should be installed globally lint :; npx solhint contracts/**/*.sol && npx solhint contracts/*.sol diff --git a/contracts/IPAccountImpl.sol b/contracts/IPAccountImpl.sol index 302371a3b..456592cdc 100644 --- a/contracts/IPAccountImpl.sol +++ b/contracts/IPAccountImpl.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; diff --git a/contracts/interfaces/IAccessController.sol b/contracts/interfaces/IAccessController.sol index 50be0deb4..1d37a4d0c 100644 --- a/contracts/interfaces/IAccessController.sol +++ b/contracts/interfaces/IAccessController.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; interface IAccessController { /// @notice Sets the permission for a specific function call diff --git a/contracts/interfaces/IIPAccount.sol b/contracts/interfaces/IIPAccount.sol index f1806043f..886bc6aca 100644 --- a/contracts/interfaces/IIPAccount.sol +++ b/contracts/interfaces/IIPAccount.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/interfaces/licensing/IParamVerifier.sol b/contracts/interfaces/licensing/IParamVerifier.sol index c5c7ca35e..fc386f31f 100644 --- a/contracts/interfaces/licensing/IParamVerifier.sol +++ b/contracts/interfaces/licensing/IParamVerifier.sol @@ -4,7 +4,10 @@ pragma solidity ^0.8.20; interface IParamVerifier { function verifyMintingParam(address caller, uint256 mintAmount, bytes memory data) external returns (bool); + function verifyLinkParentParam(address caller, bytes memory data) external returns (bool); + function verifyActivationParam(address caller, bytes memory data) external returns (bool); + function json() external view returns (string memory); } diff --git a/contracts/interfaces/modules/base/IModule.sol b/contracts/interfaces/modules/base/IModule.sol index e918fe940..4028f56f4 100644 --- a/contracts/interfaces/modules/base/IModule.sol +++ b/contracts/interfaces/modules/base/IModule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; interface IModule { function name() external returns (string memory); diff --git a/contracts/interfaces/registries/IIPAccountRegistry.sol b/contracts/interfaces/registries/IIPAccountRegistry.sol index b7dd2d230..bad40079f 100644 --- a/contracts/interfaces/registries/IIPAccountRegistry.sol +++ b/contracts/interfaces/registries/IIPAccountRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; /// @title Interface for IP Account Registry /// @notice This interface manages the registration and tracking of IP Accounts diff --git a/contracts/interfaces/registries/IModuleRegistry.sol b/contracts/interfaces/registries/IModuleRegistry.sol index 0be1988e2..1ee0b053d 100644 --- a/contracts/interfaces/registries/IModuleRegistry.sol +++ b/contracts/interfaces/registries/IModuleRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; /// @title IModuleRegistry /// @dev This interface defines the methods for a module registry in the Story Protocol. diff --git a/contracts/registries/IPAccountRegistry.sol b/contracts/registries/IPAccountRegistry.sol index 03f2e9e7c..680f4d341 100644 --- a/contracts/registries/IPAccountRegistry.sol +++ b/contracts/registries/IPAccountRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; import { IIPAccountRegistry } from "contracts/interfaces/registries/IIPAccountRegistry.sol"; import { IERC6551Registry } from "lib/reference/src/interfaces/IERC6551Registry.sol"; diff --git a/foundry.toml b/foundry.toml index caec2dd5d..9c329feba 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,5 +7,11 @@ gas_reports = ["*"] optimizer = true optimizer_runs = 20000 test = 'test' +solc = '0.8.23' + +[rpc_endpoints] +# Comment out for local development (testing requires 0xSplit forks — will add Mock soon) +# pin fork by using --fork-block-number 19042069 to reduce wait time +mainnet = "https://rpc.ankr.com/eth" # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/lcov.info b/lcov.info index aeb965f6f..b900646a9 100644 --- a/lcov.info +++ b/lcov.info @@ -1,29 +1,578 @@ TN: -SF:contracts/Counter.sol -FN:7,Counter.setNumber -FNDA:256,Counter.setNumber -DA:8,256 -FN:11,Counter.increment -FNDA:1,Counter.increment -DA:12,1 +SF:contracts/AccessController.sol +FN:101,AccessController.checkPermission +FN:37,AccessController.initialize +FN:54,AccessController.setPermission +FN:83,AccessController.getPermission +FNDA:43,AccessController.checkPermission +FNDA:18,AccessController.initialize +FNDA:27,AccessController.setPermission +FNDA:23,AccessController.getPermission +FNF:4 +FNH:4 +DA:38,18 +DA:39,18 +DA:56,27 +DA:57,1 +DA:59,26 +DA:60,1 +DA:62,25 +DA:63,1 +DA:65,24 +DA:66,1 +DA:69,23 +DA:70,1 +DA:72,22 +DA:89,23 +DA:108,43 +DA:109,1 +DA:112,42 +DA:113,1 +DA:116,41 +DA:117,24 +DA:121,17 +DA:122,4 +DA:126,13 +DA:128,11 +DA:129,2 +DA:132,9 +DA:135,6 +DA:137,3 +DA:139,2 +LF:29 +LH:29 +end_of_record +TN: +SF:contracts/IPAccountImpl.sol +FN:102,IPAccountImpl.execute +FN:117,IPAccountImpl.onERC721Received +FN:121,IPAccountImpl.onERC1155Received +FN:125,IPAccountImpl.onERC1155BatchReceived +FN:24,IPAccountImpl.supportsInterface +FN:35,IPAccountImpl.initialize +FN:44,IPAccountImpl.token +FN:66,IPAccountImpl.isValidSigner +FN:76,IPAccountImpl.owner +FN:88,IPAccountImpl._isValidSigner +FNDA:33,IPAccountImpl.execute +FNDA:4,IPAccountImpl.onERC721Received +FNDA:0,IPAccountImpl.onERC1155Received +FNDA:0,IPAccountImpl.onERC1155BatchReceived +FNDA:592,IPAccountImpl.supportsInterface +FNDA:49,IPAccountImpl.initialize +FNDA:76,IPAccountImpl.token +FNDA:3,IPAccountImpl.isValidSigner +FNDA:63,IPAccountImpl.owner +FNDA:36,IPAccountImpl._isValidSigner +FNF:10 +FNH:8 +DA:25,592 +DA:36,49 +DA:37,48 +DA:45,139 +DA:59,139 +DA:67,3 +DA:68,2 +DA:71,1 +DA:77,63 +DA:78,63 +DA:79,63 +DA:89,36 +DA:90,36 +DA:91,36 +DA:92,33 +DA:94,36 +DA:103,33 +DA:105,30 +DA:107,30 +DA:108,30 +DA:110,30 +DA:118,4 +DA:122,0 +DA:132,0 +LF:24 +LH:22 +end_of_record +TN: +SF:contracts/lib/registries/IPAccountChecker.sol +FN:20,IPAccountChecker.isRegistered +FN:33,IPAccountChecker.isIpAccount +FNDA:8,IPAccountChecker.isRegistered +FNDA:76,IPAccountChecker.isIpAccount +FNF:2 +FNH:2 +DA:26,8 +DA:37,76 +DA:38,76 +DA:39,74 +DA:40,74 +DA:41,74 +DA:42,74 +DA:43,74 +LF:8 +LH:8 +end_of_record +TN: +SF:contracts/modules/dispute-module/DisputeModule.sol +FN:131,DisputeModule.setDisputeJudgement +FN:153,DisputeModule.cancelDispute +FN:166,DisputeModule.resolveDispute +FN:50,DisputeModule.whitelistDisputeTags +FN:61,DisputeModule.whitelistArbitrationPolicy +FN:73,DisputeModule.whitelistArbitrationRelayer +FN:92,DisputeModule.raiseDispute +FNDA:2,DisputeModule.setDisputeJudgement +FNDA:2,DisputeModule.cancelDispute +FNDA:1,DisputeModule.resolveDispute +FNDA:7,DisputeModule.whitelistDisputeTags +FNDA:7,DisputeModule.whitelistArbitrationPolicy +FNDA:7,DisputeModule.whitelistArbitrationRelayer +FNDA:5,DisputeModule.raiseDispute +FNF:7 +FNH:7 +DA:51,7 +DA:53,7 +DA:62,7 +DA:64,7 +DA:77,7 +DA:78,7 +DA:80,7 +DA:100,5 +DA:101,1 +DA:103,4 +DA:105,4 +DA:106,4 +DA:108,4 +DA:110,4 +DA:120,4 +DA:124,4 +DA:132,2 +DA:135,2 +DA:136,1 +DA:139,0 +DA:145,1 +DA:154,2 +DA:157,1 +DA:167,1 +LF:24 +LH:23 +end_of_record +TN: +SF:contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol +FN:56,ArbitrationPolicySP.onRaiseDispute +FN:64,ArbitrationPolicySP.onDisputeJudgement +FN:72,ArbitrationPolicySP.onDisputeCancel +FN:76,ArbitrationPolicySP.withdraw +FNDA:4,ArbitrationPolicySP.onRaiseDispute +FNDA:1,ArbitrationPolicySP.onDisputeJudgement +FNDA:1,ArbitrationPolicySP.onDisputeCancel +FNDA:0,ArbitrationPolicySP.withdraw +FNF:4 +FNH:3 +DA:58,4 +DA:65,1 +DA:66,1 +DA:67,1 +LF:4 +LH:4 +end_of_record +TN: +SF:contracts/modules/royalty-module/RoyaltyModule.sol +FN:42,RoyaltyModule.whitelistRoyaltyPolicy +FN:54,RoyaltyModule.setRoyaltyPolicy +FN:75,RoyaltyModule.payRoyalty +FNDA:5,RoyaltyModule.whitelistRoyaltyPolicy +FNDA:4,RoyaltyModule.setRoyaltyPolicy +FNDA:3,RoyaltyModule.payRoyalty +FNF:3 +FNH:3 +DA:43,5 +DA:45,5 +DA:60,4 +DA:61,4 +DA:64,4 +DA:66,4 +DA:76,3 +LF:7 +LH:7 +end_of_record +TN: +SF:contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol +FN:56,RoyaltyPolicyLS.initPolicy +FN:76,RoyaltyPolicyLS.distributeFunds +FN:86,RoyaltyPolicyLS.claimRoyalties +FN:95,RoyaltyPolicyLS.onRoyaltyPayment +FNDA:4,RoyaltyPolicyLS.initPolicy +FNDA:2,RoyaltyPolicyLS.distributeFunds +FNDA:1,RoyaltyPolicyLS.claimRoyalties +FNDA:3,RoyaltyPolicyLS.onRoyaltyPayment +FNF:4 +FNH:4 +DA:57,4 +DA:58,4 +DA:64,4 +DA:68,4 +DA:79,2 +DA:87,1 +DA:99,3 +DA:100,3 +LF:8 +LH:8 +end_of_record +TN: +SF:contracts/registries/IPAccountRegistry.sol +FN:36,IPAccountRegistry.registerIpAccount +FN:63,IPAccountRegistry.ipAccount +FN:69,IPAccountRegistry.getIPAccountImpl +FN:73,IPAccountRegistry._get6551AccountAddress +FNDA:49,IPAccountRegistry.registerIpAccount +FNDA:157,IPAccountRegistry.ipAccount +FNDA:12,IPAccountRegistry.getIPAccountImpl +FNDA:157,IPAccountRegistry._get6551AccountAddress +FNF:4 +FNH:4 +DA:41,49 +DA:42,49 +DA:49,49 +DA:50,49 +DA:55,48 +DA:64,157 +DA:70,12 +DA:78,157 +DA:79,157 +LF:9 +LH:9 +end_of_record +TN: +SF:contracts/registries/IPRecordRegistry.sol +FN:131,IPRecordRegistry.createIPAccount +FN:145,IPRecordRegistry.setResolver +FN:173,IPRecordRegistry._createIPAccount +FN:185,IPRecordRegistry._setResolver +FN:56,IPRecordRegistry.ipId +FN:63,IPRecordRegistry.isRegistered +FN:80,IPRecordRegistry.resolver +FN:99,IPRecordRegistry.register +FNDA:4,IPRecordRegistry.createIPAccount +FNDA:4,IPRecordRegistry.setResolver +FNDA:18,IPRecordRegistry._createIPAccount +FNDA:21,IPRecordRegistry._setResolver +FNDA:15,IPRecordRegistry.ipId +FNDA:15,IPRecordRegistry.isRegistered +FNDA:6,IPRecordRegistry.resolver +FNDA:21,IPRecordRegistry.register +FNF:8 +FNH:8 +DA:57,41 +DA:64,13 +DA:73,2 +DA:74,2 +DA:81,5 +DA:90,1 +DA:91,1 +DA:106,20 +DA:107,20 +DA:108,1 +DA:113,19 +DA:115,19 +DA:116,15 +DA:118,19 +DA:119,19 +DA:120,19 +DA:132,4 +DA:134,4 +DA:135,1 +DA:137,3 +DA:151,3 +DA:152,3 +DA:159,4 +DA:160,1 +DA:163,3 +DA:164,1 +DA:166,2 +DA:178,18 +DA:179,18 +DA:186,21 +DA:187,21 +LF:31 +LH:31 +end_of_record +TN: +SF:contracts/registries/LicenseRegistry.sol +FN:103,LicenseRegistry.totalFrameworks +FN:108,LicenseRegistry.framework +FN:122,LicenseRegistry._addIdOrGetExisting +FN:143,LicenseRegistry.addPolicy +FN:162,LicenseRegistry._addPolictyId +FN:173,LicenseRegistry.totalPolicies +FN:178,LicenseRegistry.policy +FN:188,LicenseRegistry.policyIdsForIp +FN:192,LicenseRegistry.totalPoliciesForIp +FN:196,LicenseRegistry.isPolicyIdSetForIp +FN:200,LicenseRegistry.policyIdForIpAtIndex +FN:204,LicenseRegistry.policyForIpAtIndex +FN:218,LicenseRegistry.mintLicense +FN:257,LicenseRegistry.isLicensee +FN:270,LicenseRegistry.setParentPolicy +FN:317,LicenseRegistry.isParent +FN:321,LicenseRegistry.parentIpIds +FN:325,LicenseRegistry.totalParentsForIpId +FN:51,LicenseRegistry.addLicenseFramework +FN:75,LicenseRegistry._setParamArray +FNDA:1,LicenseRegistry.totalFrameworks +FNDA:1,LicenseRegistry.framework +FNDA:16,LicenseRegistry._addIdOrGetExisting +FNDA:9,LicenseRegistry.addPolicy +FNDA:12,LicenseRegistry._addPolictyId +FNDA:3,LicenseRegistry.totalPolicies +FNDA:1,LicenseRegistry.policy +FNDA:2,LicenseRegistry.policyIdsForIp +FNDA:3,LicenseRegistry.totalPoliciesForIp +FNDA:2,LicenseRegistry.isPolicyIdSetForIp +FNDA:3,LicenseRegistry.policyIdForIpAtIndex +FNDA:6,LicenseRegistry.policyForIpAtIndex +FNDA:4,LicenseRegistry.mintLicense +FNDA:2,LicenseRegistry.isLicensee +FNDA:3,LicenseRegistry.setParentPolicy +FNDA:3,LicenseRegistry.isParent +FNDA:1,LicenseRegistry.parentIpIds +FNDA:1,LicenseRegistry.totalParentsForIpId +FNDA:8,LicenseRegistry.addLicenseFramework +FNDA:24,LicenseRegistry._setParamArray +FNF:20 +FNH:20 +DA:53,8 +DA:54,0 +DA:58,8 +DA:59,8 +DA:60,8 +DA:61,8 +DA:62,8 +DA:63,8 +DA:66,8 +DA:81,24 +DA:82,0 +DA:84,24 +DA:85,24 +DA:86,8 +DA:87,16 +DA:88,8 +DA:89,8 +DA:90,8 +DA:92,0 +DA:94,24 +DA:95,22 +DA:104,1 +DA:109,13 +DA:110,13 +DA:111,0 +DA:113,0 +DA:126,16 +DA:127,16 +DA:128,16 +DA:129,4 +DA:131,12 +DA:132,12 +DA:133,12 +DA:145,12 +DA:147,12 +DA:148,12 +DA:149,12 +DA:150,8 +DA:151,8 +DA:154,12 +DA:163,12 +DA:165,12 +DA:166,0 +DA:169,12 +DA:174,3 +DA:179,8 +DA:180,8 +DA:181,0 +DA:183,0 +DA:189,2 +DA:193,3 +DA:197,2 +DA:201,3 +DA:205,6 +DA:219,4 +DA:221,4 +DA:222,4 +DA:223,4 +DA:224,0 +DA:231,4 +DA:233,4 +DA:234,4 +DA:235,4 +DA:236,4 +DA:238,4 +DA:240,4 +DA:241,0 +DA:245,4 +DA:246,4 +DA:247,4 +DA:248,4 +DA:249,4 +DA:252,4 +DA:253,4 +DA:258,2 +DA:276,3 +DA:277,3 +DA:278,3 +DA:283,3 +DA:285,3 +DA:286,3 +DA:287,3 +DA:288,2 +DA:290,2 +DA:291,2 +DA:292,0 +DA:297,3 +DA:299,3 +DA:304,3 +DA:305,3 +DA:306,0 +DA:308,3 +DA:313,3 +DA:318,3 +DA:322,1 +DA:326,1 +LF:96 +LH:84 +end_of_record +TN: +SF:contracts/registries/ModuleRegistry.sol +FN:20,ModuleRegistry.registerModule +FN:48,ModuleRegistry.removeModule +FN:67,ModuleRegistry.getModule +FN:74,ModuleRegistry.isRegistered +FNDA:35,ModuleRegistry.registerModule +FNDA:3,ModuleRegistry.removeModule +FNDA:8,ModuleRegistry.getModule +FNDA:23,ModuleRegistry.isRegistered +FNF:4 +FNH:4 +DA:22,35 +DA:23,1 +DA:25,34 +DA:26,1 +DA:28,33 +DA:29,1 +DA:31,32 +DA:32,1 +DA:34,31 +DA:35,1 +DA:37,30 +DA:38,1 +DA:40,29 +DA:41,29 +DA:43,29 +DA:49,3 +DA:50,1 +DA:53,2 +DA:54,1 +DA:57,1 +DA:58,1 +DA:59,1 +DA:61,1 +DA:68,8 +DA:75,23 +LF:25 +LH:25 +end_of_record +TN: +SF:contracts/resolvers/IPMetadataResolver.sol +FN:109,IPMetadataResolver.setMetadata +FN:116,IPMetadataResolver.setName +FN:123,IPMetadataResolver.setDescription +FN:130,IPMetadataResolver.setHash +FN:137,IPMetadataResolver.setURI +FN:144,IPMetadataResolver.supportsInterface +FN:151,IPMetadataResolver._defaultTokenURI +FN:34,IPMetadataResolver.metadata +FN:50,IPMetadataResolver.name +FN:57,IPMetadataResolver.description +FN:64,IPMetadataResolver.hash +FN:70,IPMetadataResolver.registrationDate +FN:76,IPMetadataResolver.registrant +FN:82,IPMetadataResolver.owner +FN:91,IPMetadataResolver.uri +FNDA:3,IPMetadataResolver.setMetadata +FNDA:2,IPMetadataResolver.setName +FNDA:2,IPMetadataResolver.setDescription +FNDA:2,IPMetadataResolver.setHash +FNDA:1,IPMetadataResolver.setURI +FNDA:2,IPMetadataResolver.supportsInterface +FNDA:1,IPMetadataResolver._defaultTokenURI +FNDA:1,IPMetadataResolver.metadata +FNDA:2,IPMetadataResolver.name +FNDA:2,IPMetadataResolver.description +FNDA:2,IPMetadataResolver.hash +FNDA:1,IPMetadataResolver.registrationDate +FNDA:1,IPMetadataResolver.registrant +FNDA:2,IPMetadataResolver.owner +FNDA:4,IPMetadataResolver.uri +FNF:15 +FNH:15 +DA:35,1 +DA:36,1 +DA:37,1 +DA:51,2 +DA:58,2 +DA:65,2 +DA:71,1 +DA:77,1 +DA:83,4 +DA:84,1 +DA:86,3 +DA:92,5 +DA:93,1 +DA:96,4 +DA:97,4 +DA:99,4 +DA:100,3 +DA:103,1 +DA:110,2 +DA:117,1 +DA:124,1 +DA:131,1 +DA:138,1 +DA:145,2 +DA:152,1 +DA:164,1 +DA:186,1 +DA:187,1 +LF:28 +LH:28 +end_of_record +TN: +SF:contracts/resolvers/ResolverBase.sol +FN:43,ResolverBase.accessController +FN:50,ResolverBase.supportsInterface +FNDA:1,ResolverBase.accessController +FNDA:1,ResolverBase.supportsInterface FNF:2 FNH:2 +DA:44,1 +DA:51,1 LF:2 LH:2 -BRF:0 -BRH:0 end_of_record TN: -SF:script/Counter.s.sol -FN:7,CounterScript.setUp -FNDA:0,CounterScript.setUp -FN:9,CounterScript.run -FNDA:0,CounterScript.run -DA:10,0 +SF:contracts/utils/ShortStringOps.sol +FN:12,ShortStringEquals.equal +FN:41,ShortStringEquals.stringToBytes32 +FNDA:0,ShortStringEquals.equal +FNDA:4,ShortStringEquals.stringToBytes32 FNF:2 -FNH:0 -LF:1 -LH:0 -BRF:0 -BRH:0 +FNH:1 +DA:13,0 +DA:18,0 +DA:23,0 +DA:28,0 +DA:33,0 +DA:38,0 +DA:42,4 +LF:7 +LH:1 end_of_record diff --git a/lib/forge-std/src/console.sol b/lib/forge-std/src/console.sol index ad57e5368..86ea13820 100644 --- a/lib/forge-std/src/console.sol +++ b/lib/forge-std/src/console.sol @@ -1530,4 +1530,4 @@ library console { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } -} \ No newline at end of file +} diff --git a/lib/forge-std/src/console2.sol b/lib/forge-std/src/console2.sol index 8596233d3..87423cfba 100644 --- a/lib/forge-std/src/console2.sol +++ b/lib/forge-std/src/console2.sol @@ -1543,4 +1543,4 @@ library console2 { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } -} \ No newline at end of file +} diff --git a/test/foundry/AccessController.t.sol b/test/foundry/AccessController.t.sol index 01db7558d..1063bce71 100644 --- a/test/foundry/AccessController.t.sol +++ b/test/foundry/AccessController.t.sol @@ -1,21 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; -import "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; -import "contracts/registries/IPAccountRegistry.sol"; -import "contracts/IPAccountImpl.sol"; -import "contracts/interfaces/IIPAccount.sol"; -import "lib/reference/src/interfaces/IERC6551Account.sol"; -import "test/foundry/mocks/MockERC721.sol"; import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; -import "test/foundry/mocks/MockAccessController.sol"; -import "test/foundry/mocks/MockModule.sol"; -import "test/foundry/mocks/MockOrchestratorModule.sol"; -import "contracts/interfaces/registries/IModuleRegistry.sol"; -import "contracts/registries/ModuleRegistry.sol"; -import "contracts/AccessController.sol"; -import "contracts/lib/AccessPermission.sol"; +import { IERC6551Account } from "lib/reference/src/interfaces/IERC6551Account.sol"; + +import { AccessController } from "contracts/AccessController.sol"; +import { IIPAccount } from "contracts/interfaces/IIPAccount.sol"; +import { IModuleRegistry } from "contracts/interfaces/registries/IModuleRegistry.sol"; +import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; +import { AccessPermission } from "contracts/lib/AccessPermission.sol"; +import { Errors } from "contracts/lib/Errors.sol"; +import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; +import { ModuleRegistry } from "contracts/registries/ModuleRegistry.sol"; +import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; +import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; +import { MockModule } from "test/foundry/mocks/MockModule.sol"; +import { MockOrchestratorModule } from "test/foundry/mocks/MockOrchestratorModule.sol"; contract AccessControllerTest is Test { AccessController public accessController; @@ -40,20 +42,16 @@ contract AccessControllerTest is Test { ); moduleRegistry = new ModuleRegistry(); accessController.initialize(address(ipAccountRegistry), address(moduleRegistry)); - nft.mint(owner, tokenId); + nft.mintId(owner, tokenId); address deployedAccount = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), tokenId); ipAccount = IIPAccount(payable(deployedAccount)); - mockModule = new MockModule( - address(ipAccountRegistry), - address(moduleRegistry), - "MockModule" - ); -// moduleWithoutPermission = new MockModule( -// address(ipAccountRegistry), -// address(moduleRegistry), -// "ModuleWithoutPermission" -// ); + mockModule = new MockModule(address(ipAccountRegistry), address(moduleRegistry), "MockModule"); + // moduleWithoutPermission = new MockModule( + // address(ipAccountRegistry), + // address(moduleRegistry), + // "ModuleWithoutPermission" + // ); } // test owner can set permission @@ -120,10 +118,64 @@ contract AccessControllerTest is Test { AccessPermission.ALLOW ) ); + } + + function test_AccessController_revert_directSetPermission() public { + moduleRegistry.registerModule("MockModule", address(mockModule)); + address signer = vm.addr(2); + + vm.prank(address(ipAccount)); + vm.expectRevert(Errors.AccessController__IPAccountIsZeroAddress.selector); + accessController.setPermission( + address(0), + signer, + address(mockModule), + mockModule.executeSuccessfully.selector, + AccessPermission.ALLOW + ); + + vm.prank(address(ipAccount)); + vm.expectRevert(Errors.AccessController__SignerIsZeroAddress.selector); + accessController.setPermission( + address(ipAccount), + address(0), + address(mockModule), + mockModule.executeSuccessfully.selector, + AccessPermission.ALLOW + ); + vm.prank(address(ipAccount)); + vm.expectRevert(Errors.AccessController__IPAccountIsNotValid.selector); + accessController.setPermission( + address(0xbeefbeef), + signer, + address(mockModule), + mockModule.executeSuccessfully.selector, + AccessPermission.ALLOW + ); + + vm.prank(owner); // not calling from ipAccount + vm.expectRevert(Errors.AccessController__CallerIsNotIPAccount.selector); + accessController.setPermission( + address(ipAccount), + signer, + address(mockModule), + mockModule.executeSuccessfully.selector, + AccessPermission.ALLOW + ); + + vm.prank(address(ipAccount)); // not calling from ipAccount + vm.expectRevert(Errors.AccessController__PermissionIsNotValid.selector); + accessController.setPermission( + address(ipAccount), + signer, + address(mockModule), + mockModule.executeSuccessfully.selector, + type(uint8).max + ); } - function test_AccessController_functionPermissionWildcardAllow() public { + function test_AccessController_revert_checkPermission() public { moduleRegistry.registerModule("MockModule", address(mockModule)); address signer = vm.addr(2); vm.prank(owner); @@ -139,13 +191,43 @@ contract AccessControllerTest is Test { AccessPermission.ALLOW ) ); - assertEq( - accessController.getPermission( + + assertFalse( + accessController.checkPermission( address(ipAccount), signer, + address(0xbeef), // instead of address(mockModule) + mockModule.executeSuccessfully.selector + ) + ); + assertFalse( + accessController.checkPermission( + address(0xbeef), // invalid IPAccount + signer, address(mockModule), - bytes4(0) - ), + mockModule.executeSuccessfully.selector + ) + ); + } + + function test_AccessController_functionPermissionWildcardAllow() public { + moduleRegistry.registerModule("MockModule", address(mockModule)); + address signer = vm.addr(2); + vm.prank(owner); + ipAccount.execute( + address(accessController), + 0, + abi.encodeWithSignature( + "setPermission(address,address,address,bytes4,uint8)", + address(ipAccount), + signer, + address(mockModule), + bytes4(0), + AccessPermission.ALLOW + ) + ); + assertEq( + accessController.getPermission(address(ipAccount), signer, address(mockModule), bytes4(0)), AccessPermission.ALLOW ); assertEq( @@ -185,12 +267,7 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(mockModule), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(mockModule), bytes4(0)), AccessPermission.DENY ); assertEq( @@ -230,12 +307,7 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(0), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(0), bytes4(0)), AccessPermission.ALLOW ); assertEq( @@ -275,12 +347,7 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(0), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(0), bytes4(0)), AccessPermission.DENY ); assertEq( @@ -333,12 +400,7 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(mockModule), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(mockModule), bytes4(0)), AccessPermission.DENY ); @@ -363,7 +425,6 @@ contract AccessControllerTest is Test { ); } - function test_AccessController_overrideFunctionWildcard_DenyOverrideAllow() public { moduleRegistry.registerModule("MockModule", address(mockModule)); address signer = vm.addr(2); @@ -394,12 +455,7 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(mockModule), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(mockModule), bytes4(0)), AccessPermission.ALLOW ); @@ -454,12 +510,7 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(0), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(0), bytes4(0)), AccessPermission.DENY ); @@ -484,7 +535,6 @@ contract AccessControllerTest is Test { ); } - function test_AccessController_overrideToAddressWildcard_DenyOverrideAllow() public { moduleRegistry.registerModule("MockModule", address(mockModule)); address signer = vm.addr(2); @@ -515,12 +565,7 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(0), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(0), bytes4(0)), AccessPermission.ALLOW ); @@ -545,7 +590,6 @@ contract AccessControllerTest is Test { ); } - function test_AccessController_functionWildcardOverrideToAddressWildcard_allowOverrideDeny() public { moduleRegistry.registerModule("MockModule", address(mockModule)); address signer = vm.addr(2); @@ -576,22 +620,12 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(0), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(0), bytes4(0)), AccessPermission.DENY ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(mockModule), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(mockModule), bytes4(0)), AccessPermission.ALLOW ); @@ -646,22 +680,12 @@ contract AccessControllerTest is Test { ) ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(0), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(0), bytes4(0)), AccessPermission.ALLOW ); assertEq( - accessController.getPermission( - address(ipAccount), - signer, - address(mockModule), - bytes4(0) - ), + accessController.getPermission(address(ipAccount), signer, address(mockModule), bytes4(0)), AccessPermission.DENY ); @@ -693,10 +717,7 @@ contract AccessControllerTest is Test { bytes memory result = ipAccount.execute( address(mockModule), 0, - abi.encodeWithSignature( - "executeSuccessfully(string)", - "testParameter" - ) + abi.encodeWithSignature("executeSuccessfully(string)", "testParameter") ); assertEq("testParameter", abi.decode(result, (string))); } @@ -728,10 +749,7 @@ contract AccessControllerTest is Test { bytes memory result = ipAccount.execute( address(mockModule), 0, - abi.encodeWithSignature( - "callAnotherModule(string)", - "AnotherMockModule" - ) + abi.encodeWithSignature("callAnotherModule(string)", "AnotherMockModule") ); assertEq("AnotherMockModule", abi.decode(result, (string))); } @@ -844,7 +862,5 @@ contract AccessControllerTest is Test { vm.prank(owner); vm.expectRevert("Invalid signer"); mockOrchestratorModule.workflowFailure(payable(address(ipAccount))); - } - } diff --git a/test/foundry/DisputeModule.t.sol b/test/foundry/DisputeModule.t.sol index 8060b6fd8..f1548d3af 100644 --- a/test/foundry/DisputeModule.t.sol +++ b/test/foundry/DisputeModule.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.23; -import {console2} from "forge-std/console2.sol"; -import {TestHelper} from "./../utils/TestHelper.sol"; - -import {ShortStringOps} from "./../../contracts/utils/ShortStringOps.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Errors} from "contracts/lib/Errors.sol"; +import {ShortStringOps} from "contracts/utils/ShortStringOps.sol"; +import {TestHelper} from "test/utils/TestHelper.sol"; + contract TestDisputeModule is TestHelper { function setUp() public override { super.setUp(); @@ -50,6 +50,9 @@ contract TestDisputeModule is TestHelper { uint256 ipAccount1USDCBalanceBefore = IERC20(USDC).balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceBefore = IERC20(USDC).balanceOf(address(arbitrationPolicySP)); + vm.expectRevert(Errors.DisputeModule__NotWhitelistedArbitrationPolicy.selector); + disputeModule.raiseDispute(address(1), address(0xbeef), string("urlExample"), "plagiarism", ""); + disputeModule.raiseDispute(address(1), address(arbitrationPolicySP), string("urlExample"), "plagiarism", ""); uint256 disputeIdAfter = disputeModule.disputeId(); @@ -81,6 +84,12 @@ contract TestDisputeModule is TestHelper { uint256 ipAccount1USDCBalanceBefore = IERC20(USDC).balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceBefore = IERC20(USDC).balanceOf(address(arbitrationPolicySP)); + vm.startPrank(address(0xdeadbeef)); + vm.expectRevert(Errors.DisputeModule__NotWhitelistedArbitrationRelayer.selector); + disputeModule.setDisputeJudgement(1, false, ""); + vm.stopPrank(); + + vm.startPrank(arbitrationRelayer); disputeModule.setDisputeJudgement(1, true, ""); uint256 ipAccount1USDCBalanceAfter = IERC20(USDC).balanceOf(ipAccount1); @@ -90,4 +99,37 @@ contract TestDisputeModule is TestHelper { assertEq(ipAccount1USDCBalanceAfter - ipAccount1USDCBalanceBefore, ARBITRATION_PRICE); assertEq(arbitrationPolicySPUSDCBalanceBefore - arbitrationPolicySPUSDCBalanceAfter, ARBITRATION_PRICE); } + + // TODO + function test_DisputeModule_cancelDispute() public { + // raise dispute + vm.startPrank(ipAccount1); + IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); + disputeModule.raiseDispute(address(1), address(arbitrationPolicySP), string("urlExample"), "plagiarism", ""); + vm.stopPrank(); + + // cancel dispute + + vm.prank(address(0xdeadbeef)); + vm.expectRevert(Errors.DisputeModule__NotDisputeInitiator.selector); + disputeModule.cancelDispute(1, ""); + + vm.startPrank(ipAccount1); + disputeModule.cancelDispute(1, ""); + vm.stopPrank(); + } + + // TODO + function test_DisputeModule_resolveDispute() public { + // raise dispute + vm.startPrank(ipAccount1); + IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); + disputeModule.raiseDispute(address(1), address(arbitrationPolicySP), string("urlExample"), "plagiarism", ""); + vm.stopPrank(); + + // resolve dispute + vm.prank(address(0xdeadbeef)); + vm.expectRevert(Errors.DisputeModule__NotDisputeInitiator.selector); + disputeModule.resolveDispute(1); + } } diff --git a/test/foundry/IPAccount.t.sol b/test/foundry/IPAccount.t.sol index cf76c08a9..624551fa1 100644 --- a/test/foundry/IPAccount.t.sol +++ b/test/foundry/IPAccount.t.sol @@ -3,15 +3,17 @@ pragma solidity ^0.8.21; import "forge-std/Test.sol"; -import "contracts/registries/IPAccountRegistry.sol"; +import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; +import "lib/reference/src/interfaces/IERC6551Account.sol"; + import "contracts/IPAccountImpl.sol"; import "contracts/interfaces/IIPAccount.sol"; -import "lib/reference/src/interfaces/IERC6551Account.sol"; -import "test/foundry/mocks/MockERC721.sol"; -import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; +import "contracts/registries/IPAccountRegistry.sol"; +import "contracts/registries/ModuleRegistry.sol"; + import "test/foundry/mocks/MockAccessController.sol"; +import "test/foundry/mocks/MockERC721.sol"; import "test/foundry/mocks/MockModule.sol"; -import "contracts/registries/ModuleRegistry.sol"; contract IPAccountTest is Test { IPAccountRegistry public registry; @@ -22,7 +24,6 @@ contract IPAccountTest is Test { ModuleRegistry public moduleRegistry = new ModuleRegistry(); MockModule public module; - function setUp() public { implementation = new IPAccountImpl(); registry = new IPAccountRegistry(address(erc6551Registry), address(accessController), address(implementation)); @@ -39,7 +40,7 @@ contract IPAccountTest is Test { tokenId ); - nft.mint(owner, tokenId); + nft.mintId(owner, tokenId); vm.prank(owner, owner); @@ -66,7 +67,7 @@ contract IPAccountTest is Test { address owner = vm.addr(1); uint256 tokenId = 100; - nft.mint(owner, tokenId); + nft.mintId(owner, tokenId); vm.prank(owner, owner); address account = registry.registerIpAccount( @@ -82,6 +83,7 @@ contract IPAccountTest is Test { assertEq(chainId_, block.chainid); assertEq(tokenAddress_, address(nft)); assertEq(tokenId_, tokenId); + assertEq(ipAccount.isValidSigner(vm.addr(2), ""), bytes4(0)); assertEq(ipAccount.isValidSigner(owner, ""), IERC6551Account.isValidSigner.selector); // Transfer token to new owner and make sure account owner changes @@ -98,7 +100,7 @@ contract IPAccountTest is Test { address owner = vm.addr(1); uint256 tokenId = 100; - nft.mint(owner, tokenId); + nft.mintId(owner, tokenId); vm.prank(owner, owner); address account = registry.registerIpAccount( @@ -108,7 +110,7 @@ contract IPAccountTest is Test { ); uint256 subTokenId = 111; - nft.mint(account, subTokenId); + nft.mintId(account, subTokenId); IIPAccount ipAccount = IIPAccount(payable(account)); @@ -123,7 +125,7 @@ contract IPAccountTest is Test { address owner = vm.addr(1); uint256 tokenId = 100; - nft.mint(owner, tokenId); + nft.mintId(owner, tokenId); address account = registry.registerIpAccount( block.chainid, @@ -132,7 +134,7 @@ contract IPAccountTest is Test { ); uint256 subTokenId = 111; - nft.mint(account, subTokenId); + nft.mintId(account, subTokenId); IIPAccount ipAccount = IIPAccount(payable(account)); @@ -146,7 +148,7 @@ contract IPAccountTest is Test { address owner = vm.addr(1); uint256 tokenId = 100; - nft.mint(owner, tokenId); + nft.mintId(owner, tokenId); address account = registry.registerIpAccount( block.chainid, @@ -155,7 +157,7 @@ contract IPAccountTest is Test { ); uint256 subTokenId = 111; - nft.mint(account, subTokenId); + nft.mintId(account, subTokenId); IIPAccount ipAccount = IIPAccount(payable(account)); @@ -169,7 +171,7 @@ contract IPAccountTest is Test { address owner = vm.addr(1); uint256 tokenId = 100; - nft.mint(owner, tokenId); + nft.mintId(owner, tokenId); vm.prank(owner, owner); address account = registry.registerIpAccount( @@ -180,10 +182,10 @@ contract IPAccountTest is Test { address otherOwner = vm.addr(2); uint256 otherTokenId = 200; - nft.mint(otherOwner, otherTokenId); + nft.mintId(otherOwner, otherTokenId); vm.prank(otherOwner); nft.safeTransferFrom(otherOwner, account, otherTokenId); assertEq(nft.balanceOf(account), 1); assertEq(nft.ownerOf(otherTokenId), account); } -} +} \ No newline at end of file diff --git a/test/foundry/IPAccount.tree b/test/foundry/IPAccount.tree new file mode 100644 index 000000000..55d180b5f --- /dev/null +++ b/test/foundry/IPAccount.tree @@ -0,0 +1,23 @@ +IPAccount +├── when caller is not IPAccount NFT owner +│ ├── when caller registers IPAccount +│ │ └── it should revert +│ └── when caller transfers IPAccount +│ └── it should revert +└── when caller is IPAccount NFT owner + ├── when owner registers IPAccount + │ ├── it should create valid IPAccount + │ └── it should succeed + ├── when owner transfers IPAccount + │ ├── it should make owner no longer IPAccount NFT owner + │ └── it should succeed + └── when owner calls execute + ├── given the abi data is empty + │ └── it should revert + ├── given the abi data is invalid + │ └── it should revert + ├── given the abi encoded module is invalid + │ └── it should revert + └── given the abi encoded module is valid + ├── it should emit an event + └── it should succeed \ No newline at end of file diff --git a/test/foundry/IPRecordRegistry.t.sol b/test/foundry/IPRecordRegistry.t.sol index 12f96a34e..7f23c2581 100644 --- a/test/foundry/IPRecordRegistry.t.sol +++ b/test/foundry/IPRecordRegistry.t.sol @@ -66,7 +66,9 @@ contract IPRecordRegistryTest is BaseTest { ); MockERC721 erc721 = new MockERC721(); tokenAddress = address(erc721); - tokenId = erc721.mint(alice, 99); + tokenId = erc721.mintId(alice, 99); + + assertEq(ipAccountRegistry.getIPAccountImpl(), ipAccountImpl); } /// @notice Tests IP record registry initialization. diff --git a/test/foundry/LicenseRegistry.t.sol b/test/foundry/LicenseRegistry.t.sol index 3a35292c6..87a0a11b6 100644 --- a/test/foundry/LicenseRegistry.t.sol +++ b/test/foundry/LicenseRegistry.t.sol @@ -87,7 +87,6 @@ contract LicenseRegistryTest is Test { assertEq(indexOnIpId, 0, "indexOnIpId not 0"); Licensing.Policy memory storedPolicy = registry.policy(policyId); assertEq(keccak256(abi.encode(storedPolicy)), keccak256(abi.encode(policy)), "policy not stored properly"); - } function test_LicenseRegistry_addSamePolicyReusesPolicyId() public withFrameworkParams { @@ -140,7 +139,7 @@ contract LicenseRegistryTest is Test { assertEq(registry.policyIdForIpAtIndex(ipId1, 1), 2, "policyIdForIpAtIndex not 2"); } - function test_LicenseRegistry_mintLicense() public withFrameworkParams returns(uint256 licenseId) { + function test_LicenseRegistry_mintLicense() public withFrameworkParams returns (uint256 licenseId) { Licensing.Policy memory policy = Licensing.Policy({ frameworkId: 1, mintingParamValues: new bytes[](1), @@ -151,8 +150,15 @@ contract LicenseRegistryTest is Test { policy.mintingParamValues[0] = abi.encode(true); policy.activationParamValues[0] = abi.encode(true); policy.linkParentParamValues[0] = abi.encode(true); + (uint256 policyId, uint256 indexOnIpId) = registry.addPolicy(ipId1, policy); assertEq(policyId, 1); + assertTrue(registry.isPolicyIdSetForIp(ipId1, policyId)); + + uint256[] memory policyIds = registry.policyIdsForIp(ipId1); + assertEq(policyIds.length, 1); + assertEq(policyIds[indexOnIpId], policyId); + Licensing.License memory licenseData = Licensing.License({ policyId: policyId, licensorIpIds: new address[](1) @@ -162,6 +168,7 @@ contract LicenseRegistryTest is Test { licenseId = registry.mintLicense(licenseData, 2, licenseHolder); assertEq(licenseId, 1); assertEq(registry.balanceOf(licenseHolder, licenseId), 2); + assertEq(registry.isLicensee(licenseId, licenseHolder), true); return licenseId; } @@ -178,6 +185,13 @@ contract LicenseRegistryTest is Test { "policy not copied" ); + address[] memory parents = registry.parentIpIds(ipId2); + assertEq(parents.length, 1, "not 1 parent"); + assertEq( + parents.length, + registry.totalParentsForIpId(ipId2), + "parents.length and totalParentsForIpId mismatch" + ); + assertEq(parents[0], ipId1, "parent not ipId1"); } - } diff --git a/test/foundry/ModuleRegistry.t.sol b/test/foundry/ModuleRegistry.t.sol index 13cd53a9d..242186265 100644 --- a/test/foundry/ModuleRegistry.t.sol +++ b/test/foundry/ModuleRegistry.t.sol @@ -1,18 +1,20 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; -import "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; -import "contracts/registries/IPAccountRegistry.sol"; -import "contracts/IPAccountImpl.sol"; -import "contracts/interfaces/IIPAccount.sol"; -import "lib/reference/src/interfaces/IERC6551Account.sol"; -import "test/foundry/mocks/MockERC721.sol"; import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; -import "test/foundry/mocks/MockAccessController.sol"; -import "test/foundry/mocks/MockModule.sol"; -import "contracts/registries/ModuleRegistry.sol"; -import "contracts/lib/Errors.sol"; +import { IERC6551Account } from "lib/reference/src/interfaces/IERC6551Account.sol"; + +import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; +import { IIPAccount } from "contracts/interfaces/IIPAccount.sol"; +import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; +import { ModuleRegistry } from "contracts/registries/ModuleRegistry.sol"; +import { Errors } from "contracts/lib/Errors.sol"; + +import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; +import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; +import { MockModule } from "test/foundry/mocks/MockModule.sol"; contract ModuleRegistryTest is Test { IPAccountRegistry public registry; @@ -34,14 +36,37 @@ contract ModuleRegistryTest is Test { assertTrue(moduleRegistry.isRegistered(address(module))); } - function test_ModuleRegistry_revert_registerModule_ifModuleAlreadyRegistered() public { + function test_ModuleRegistry_revert_registerModule_moduleAddressIsZero() public { + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__ModuleAddressZeroAddress.selector)); + moduleRegistry.registerModule("MockModule", address(0)); + } + + function test_ModuleRegistry_revert_registerModule_moduleAddressIsNotContract() public { + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__ModuleAddressNotContract.selector)); + moduleRegistry.registerModule("MockModule", address(0xbeefbeef)); + } + + function test_ModuleRegistry_revert_registerModule_moduleAlreadyRegistered() public { moduleRegistry.registerModule("MockModule", address(module)); - vm.expectRevert( - abi.encodeWithSelector( - Errors.ModuleRegistry__ModuleAlreadyRegistered.selector - ) - ); + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__ModuleAlreadyRegistered.selector)); + moduleRegistry.registerModule("MockModule", address(module)); + } + + function test_ModuleRegistry_revert_registerModule_nameEmptyString() public { + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__NameEmptyString.selector)); + moduleRegistry.registerModule("", address(module)); + } + + function test_ModuleRegistry_revert_registerModule_nameAlreadyRegistered() public { moduleRegistry.registerModule("MockModule", address(module)); + MockModule newModule = new MockModule(address(registry), address(moduleRegistry), "NewMockModule"); + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__NameAlreadyRegistered.selector)); + moduleRegistry.registerModule("MockModule", address(newModule)); + } + + function test_ModuleRegistry_revert_registerModule_nameDoesNotMatch() public { + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__NameDoesNotMatch.selector)); + moduleRegistry.registerModule("WrongMockModuleName", address(module)); } function test_ModuleRegistry_removeModule() public { @@ -53,4 +78,13 @@ contract ModuleRegistryTest is Test { assertFalse(moduleRegistry.isRegistered(address(module))); } + function test_ModuleRegistry_revert_removeModule_nameEmptyString() public { + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__NameEmptyString.selector)); + moduleRegistry.removeModule(""); + } + + function test_ModuleRegistry_revert_removeModule_moduleNotRegistered() public { + vm.expectRevert(abi.encodeWithSelector(Errors.ModuleRegistry__ModuleNotRegistered.selector)); + moduleRegistry.removeModule("MockModuleMockMock"); + } } diff --git a/test/foundry/integration/Integration.t.sol b/test/foundry/integration/Integration.t.sol new file mode 100644 index 000000000..a86d3ac21 --- /dev/null +++ b/test/foundry/integration/Integration.t.sol @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.23; + +import { Test } from "forge-std/Test.sol"; + +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; +import { IERC6551Account } from "lib/reference/src/interfaces/IERC6551Account.sol"; + +import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; +import { IIPAccount } from "contracts/interfaces/IIPAccount.sol"; +import { IParamVerifier } from "contracts/interfaces/licensing/IParamVerifier.sol"; +import { Licensing } from "contracts/lib/Licensing.sol"; +import { DisputeModule } from "contracts/modules/dispute-module/DisputeModule.sol"; +import { ArbitrationPolicySP } from "contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol"; +import { RoyaltyModule } from "contracts/modules/royalty-module/RoyaltyModule.sol"; +import { RoyaltyPolicyLS } from "contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol"; +import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; +import { LicenseRegistry } from "contracts/registries/LicenseRegistry.sol"; + +import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; +import { MockERC20 } from "test/foundry/mocks/MockERC20.sol"; +import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; +import { MockModule } from "test/foundry/mocks/MockModule.sol"; +import { MockIParamVerifier } from "test/foundry/mocks/licensing/MockParamVerifier.sol"; +import { MintPaymentVerifier } from "test/foundry/mocks/licensing/MintPaymentVerifier.sol"; +import { Users, UsersLib } from "test/foundry/utils/Users.sol"; + +struct MockERC721s { + MockERC721 a; + MockERC721 b; + MockERC721 c; +} + +contract IntegrationTest is Test { + using EnumerableSet for EnumerableSet.UintSet; + + ERC6551Registry public erc6551Registry = new ERC6551Registry(); + IPAccountImpl internal ipacctImpl; + IPAccountRegistry internal ipacctRegistry; + LicenseRegistry internal licenseRegistry; + + DisputeModule public disputeModule; + ArbitrationPolicySP public arbitrationPolicySP; + RoyaltyModule public royaltyModule; + RoyaltyPolicyLS public royaltyPolicyLS; + + MockAccessController internal accessController = new MockAccessController(); + MockERC20 internal mockToken = new MockERC20(); + MockERC721s internal nft; + MockIParamVerifier public mockLicenseVerifier = new MockIParamVerifier(); + MintPaymentVerifier internal mintPaymentVerifier; + // MockModule internal module = new MockModule(address(registry), address(moduleRegistry), "MockModule"); + + Users internal u; + + // USDC (ETH Mainnet) + address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address public constant USDC_RICH = 0xcEe284F754E854890e311e3280b767F80797180d; + // Liquid Split (ETH Mainnet) + address public constant LIQUID_SPLIT_FACTORY = 0xdEcd8B99b7F763e16141450DAa5EA414B7994831; + address public constant LIQUID_SPLIT_MAIN = 0x2ed6c4B5dA6378c7897AC67Ba9e43102Feb694EE; + + uint256 public constant ARBITRATION_PRICE = 1000 * 10 ** 6; // 1000 USDC + + // IPAccounts + mapping(address userAddr => mapping(MockERC721 nft => mapping(uint256 tokenId => address ipId))) internal ipacct; + + // NFT Token IDs List by User + mapping(address userAddr => mapping(MockERC721 nft => uint256[] tokenIds)) internal token; + + // NFT Token IDs Set by User + mapping(address userAddr => mapping(MockERC721 nft => EnumerableSet.UintSet tokenIdSet)) internal tokenSet; + + mapping(string frameworkName => Licensing.FrameworkCreationParams) internal licenseFwCreations; + + mapping(string frameworkName => uint256 frameworkId) internal licenseFwIds; + mapping(uint256 frameworkId => string frameworkName) internal licenseFwNames; // reverse of licenseFwIds + + mapping(string policyName => Licensing.Policy) internal policy; + + mapping(string policyName => mapping(address userAddr => uint256 policyId)) internal policyIds; + + mapping(address userAddr => mapping(uint256 policyId => uint256 licenseId)) internal licenseIds; + + mapping(address userAddr => uint256 balance) internal mockTokenBalanceBefore; + + mapping(address userAddr => uint256 balance) internal mockTokenBalanceAfter; + + function setUp() public { + ipacctImpl = new IPAccountImpl(); + ipacctRegistry = new IPAccountRegistry( + address(erc6551Registry), + address(accessController), + address(ipacctImpl) + ); + licenseRegistry = new LicenseRegistry("https://example-license-registry.com/{id}.json"); + + disputeModule = new DisputeModule(); + arbitrationPolicySP = new ArbitrationPolicySP(address(disputeModule), USDC, ARBITRATION_PRICE); + royaltyModule = new RoyaltyModule(); + royaltyPolicyLS = new RoyaltyPolicyLS(address(royaltyModule), LIQUID_SPLIT_FACTORY, LIQUID_SPLIT_MAIN); + + nft = MockERC721s({ a: new MockERC721(), b: new MockERC721(), c: new MockERC721() }); + u = UsersLib.createMockUsers(vm); + + royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLS), true); + + mockToken.mint(u.alice, 1000 * 10 ** mockToken.decimals()); + mockToken.mint(u.bob, 1000 * 10 ** mockToken.decimals()); + mockToken.mint(u.carl, 1000 * 10 ** mockToken.decimals()); + + // 1 mock token payment per mint + mintPaymentVerifier = new MintPaymentVerifier(address(mockToken), 1 * 10 ** mockToken.decimals()); + + vm.label(address(disputeModule), "disputeModule"); + vm.label(address(arbitrationPolicySP), "arbitrationPolicySP"); + vm.label(address(royaltyModule), "royaltyModule"); + vm.label(address(royaltyPolicyLS), "royaltyPolicyLS"); + } + + /*////////////////////////////////////////////////////////////////////////// + HELPERS + //////////////////////////////////////////////////////////////////////////*/ + + function mintNFT(address to, MockERC721 mnft) internal returns (uint256 tokenId) { + tokenId = mnft.mint(to); + token[to][mnft].push(tokenId); + tokenSet[to][mnft].add(tokenId); + } + + function transferNFT(address from, address to, MockERC721 mnft, uint256 tokenId) internal { + removeIdFromTokenData(from, mnft, tokenId); + token[to][mnft].push(tokenId); + tokenSet[to][mnft].add(tokenId); + mnft.transferFrom(from, to, tokenId); + } + + function removeIdFromTokenData(address user, MockERC721 mnft, uint256 tokenId) internal { + uint256[] storage arr = token[user][mnft]; + require(tokenSet[user][mnft].contains(tokenId), "tokenId not found in tokenSet"); + uint256 index = tokenId - 1; // tokenId starts from 1 + require(index < arr.length, "tokenId index out of range"); + // remove certain index from array while maintaing order + for (uint256 i = index; i < arr.length - 1; ++i) { + arr[i] = arr[i + 1]; + } + arr.pop(); + // delete arr[arr.length - 1]; + // arr.length--; + tokenSet[user][mnft].remove(tokenId); + } + + function registerIPAccount(address user, MockERC721 mnft, uint256 tokenId) public returns (address ipId) { + ipId = ipacctRegistry.registerIpAccount(block.chainid, address(mnft), tokenId); + ipacct[user][mnft][tokenId] = ipId; + // assertTrue(ipId != address(0)); + assertEq(ipId, ipacctRegistry.ipAccount(block.chainid, address(mnft), tokenId)); + } + + function getIpId(address user, MockERC721 mnft, uint256 tokenId) public view returns (address ipId) { + require(mnft.ownerOf(tokenId) == user, "getIpId: not owner"); + ipId = ipacct[user][mnft][tokenId]; + require(ipId == ipacctRegistry.ipAccount(block.chainid, address(mnft), tokenId)); + } + + function addLicenseFramework( + string memory name, + Licensing.FrameworkCreationParams memory params + ) public returns (uint256 fwId) { + require(licenseFwIds[name] == 0, "Framework already exists"); + licenseFwCreations[name] = params; + fwId = licenseRegistry.addLicenseFramework(params); + + licenseFwIds[name] = fwId; + licenseFwNames[fwId] = name; + } + + function attachPolicyToIPID( + address ipId, + string memory policyName + ) public returns (uint256 policyId, uint256 indexOnIpId) { + (policyId, indexOnIpId) = licenseRegistry.addPolicy(ipId, policy[policyName]); + policyIds[policyName][ipId] = policyId; + return (policyId, indexOnIpId); + } + + function attachPolicyAndMintLicenseForIPID( + address ipId, + string memory policyName, + address licensee, + uint256 amount + ) public returns (uint256 licenseId) { + (uint256 policyId, uint256 indexOnIpId) = attachPolicyToIPID(ipId, policyName); + Licensing.License memory licenseData = Licensing.License({ + policyId: policyId, + licensorIpIds: new address[](1) + }); + licenseData.licensorIpIds[0] = ipId; + licenseId = licenseRegistry.mintLicense(licenseData, amount, licensee); + licenseIds[licensee][policyId] = licenseId; + } + + function test_Integration_helper_mintNFT() public { + for (uint256 i = 1; i < 3; ++i) { + assertEq(mintNFT(u.alice, nft.a), i); + assertEq(mintNFT(u.bob, nft.b), i); + assertEq(token[u.alice][nft.a].length, i); + assertEq(token[u.bob][nft.b].length, i); + assertEq(tokenSet[u.alice][nft.a].length(), i); + assertEq(tokenSet[u.bob][nft.b].length(), i); + } + mintNFT(u.bob, nft.c); + + assertEq(token[u.alice][nft.a].length, 2); + assertEq(token[u.bob][nft.b].length, 2); + assertEq(token[u.bob][nft.c][0], 1); + assertEq(tokenSet[u.alice][nft.a].length(), 2); + assertEq(tokenSet[u.bob][nft.b].length(), 2); + assertEq(tokenSet[u.bob][nft.c].length(), 1); + } + + function test_Integration_helper_transferNFT() public { + mintNFT(u.alice, nft.a); + mintNFT(u.bob, nft.b); + + transferNFT(u.alice, u.bob, nft.a, 1); + assertEq(token[u.alice][nft.a].length, 0); + assertEq(token[u.alice][nft.b].length, 0); + assertEq(token[u.bob][nft.a][0], 1); + assertEq(token[u.bob][nft.b][0], 1); + assertEq(tokenSet[u.alice][nft.a].length(), 0); + assertEq(tokenSet[u.alice][nft.b].length(), 0); + assertEq(tokenSet[u.bob][nft.a].length(), 1); + assertEq(tokenSet[u.bob][nft.b].length(), 1); + + transferNFT(u.bob, u.alice, nft.b, 1); + assertEq(token[u.alice][nft.a].length, 0); + assertEq(token[u.alice][nft.b].length, 1); + assertEq(token[u.bob][nft.a].length, 1); + assertEq(token[u.bob][nft.b].length, 0); + assertEq(token[u.bob][nft.a][0], 1); + assertEq(tokenSet[u.alice][nft.a].length(), 0); + assertEq(tokenSet[u.alice][nft.b].length(), 1); + assertEq(tokenSet[u.bob][nft.a].length(), 1); + assertEq(tokenSet[u.bob][nft.b].length(), 0); + } + + /*////////////////////////////////////////////////////////////////////////// + INTEGRATION + //////////////////////////////////////////////////////////////////////////*/ + + function test_Integration_Full() public { + /*/////////////////////////////////////////////////////////////// + CREATE IPACCOUNTS + ////////////////////////////////////////////////////////////////*/ + + mintNFT(u.alice, nft.a); // nft a, id 1 (alice) + mintNFT(u.bob, nft.a); // nft a, id 2 (bob) + mintNFT(u.alice, nft.b); // nft b, id 1 (alice) + mintNFT(u.carl, nft.c); // nft c, id 1 (carl) + mintNFT(u.bob, nft.c); // nft c, id 2 (bob) + + registerIPAccount(u.alice, nft.a, token[u.alice][nft.a][0]); + registerIPAccount(u.bob, nft.a, token[u.bob][nft.a][0]); + registerIPAccount(u.carl, nft.c, token[u.carl][nft.c][0]); + registerIPAccount(u.bob, nft.c, token[u.bob][nft.c][0]); + + /*/////////////////////////////////////////////////////////////// + CREATE LICENSE FRAMEWORKS + ////////////////////////////////////////////////////////////////*/ + + // All trues for MockVerifier means it will always return true on condition checks + bytes[] memory byteValueTrue = new bytes[](1); + byteValueTrue[0] = abi.encode(true); + + Licensing.FrameworkCreationParams memory fwAllTrue = Licensing.FrameworkCreationParams({ + mintingParamVerifiers: new IParamVerifier[](1), + mintingParamDefaultValues: byteValueTrue, + activationParamVerifiers: new IParamVerifier[](1), + activationParamDefaultValues: byteValueTrue, + defaultNeedsActivation: true, + linkParentParamVerifiers: new IParamVerifier[](1), + linkParentParamDefaultValues: byteValueTrue, + licenseUrl: "https://very-nice-verifier-license.com" + }); + + fwAllTrue.mintingParamVerifiers[0] = mockLicenseVerifier; + fwAllTrue.activationParamVerifiers[0] = mockLicenseVerifier; + fwAllTrue.linkParentParamVerifiers[0] = mockLicenseVerifier; + + Licensing.FrameworkCreationParams memory fwMintPayment = Licensing.FrameworkCreationParams({ + mintingParamVerifiers: new IParamVerifier[](1), + mintingParamDefaultValues: byteValueTrue, // value here doesn't matter for MintPaymentVerifier + activationParamVerifiers: new IParamVerifier[](0), + activationParamDefaultValues: new bytes[](0), + defaultNeedsActivation: false, + linkParentParamVerifiers: new IParamVerifier[](0), + linkParentParamDefaultValues: new bytes[](0), + licenseUrl: "https://expensive-minting-license.com" + }); + + fwMintPayment.mintingParamVerifiers[0] = mintPaymentVerifier; + + addLicenseFramework("all_true", fwAllTrue); + addLicenseFramework("mint_payment", fwMintPayment); + + /*/////////////////////////////////////////////////////////////// + CREATE POLICIES + ////////////////////////////////////////////////////////////////*/ + + policy["test_true"] = Licensing.Policy({ + frameworkId: licenseFwIds["all_true"], + mintingParamValues: new bytes[](1), + activationParamValues: new bytes[](1), + needsActivation: true, + linkParentParamValues: new bytes[](1) + }); + + // TODO: test failure (verifier condition check fails) by setting to false + policy["test_true"].mintingParamValues[0] = abi.encode(true); + policy["test_true"].activationParamValues[0] = abi.encode(true); + policy["test_true"].linkParentParamValues[0] = abi.encode(true); + + policy["expensive_mint"] = Licensing.Policy({ + frameworkId: licenseFwIds["mint_payment"], + mintingParamValues: new bytes[](1), // empty value to use default value, which doesn't matter + activationParamValues: new bytes[](0), + needsActivation: false, + linkParentParamValues: new bytes[](0) + }); + + /*/////////////////////////////////////////////////////////////// + ADD POLICIES TO IPACCOUNTS + ////////////////////////////////////////////////////////////////*/ + + attachPolicyToIPID(getIpId(u.alice, nft.a, 1), "test_true"); + attachPolicyToIPID(getIpId(u.carl, nft.c, 1), "expensive_mint"); + + /*/////////////////////////////////////////////////////////////// + MINT LICENSES ON POLICIES + ////////////////////////////////////////////////////////////////*/ + + Licensing.License memory licenseData = Licensing.License({ + policyId: policyIds["test_true"][getIpId(u.alice, nft.a, 1)], + licensorIpIds: new address[](1) + }); + licenseData.licensorIpIds[0] = getIpId(u.alice, nft.a, 1); + + // Carl mints 1 license for policy "test_true" on alice's NFT A IPAccount + licenseIds[u.carl][policyIds["test_true"][getIpId(u.alice, nft.a, 1)]] = licenseRegistry.mintLicense( + licenseData, + 1, + u.carl + ); + + /*/////////////////////////////////////////////////////////////// + LINK IPACCOUNTS TO PARENTS USING LICENSES + ////////////////////////////////////////////////////////////////*/ + + // Carl activates above license on his NFT C IPAccount, linking as child to Alice's NFT A IPAccount + + vm.prank(u.carl); + licenseRegistry.setParentPolicy( + licenseIds[u.carl][policyIds["test_true"][getIpId(u.alice, nft.a, 1)]], + getIpId(u.carl, nft.c, 1), + u.carl + ); + assertEq( + licenseRegistry.balanceOf(u.carl, licenseIds[u.carl][policyIds["test_true"][getIpId(u.alice, nft.a, 1)]]), + 0, + "not burnt" + ); + assertTrue(licenseRegistry.isParent(getIpId(u.alice, nft.a, 1), getIpId(u.carl, nft.c, 1)), "not parent"); + assertEq( + keccak256(abi.encode(licenseRegistry.policyForIpAtIndex(getIpId(u.alice, nft.a, 1), 0))), + keccak256(abi.encode(licenseRegistry.policyForIpAtIndex(getIpId(u.carl, nft.c, 1), 1))), + // id 1, since Carl had a local policy before attaching Alice's policy + "policy not copied" + ); + + policyIds["expensive_mint"][getIpId(u.carl, nft.c, 1)] = licenseRegistry.policyIdForIpAtIndex( + getIpId(u.carl, nft.c, 1), + 0 + ); + + // Bob mints 2 license for policy happy on Carl's NFT C IPAccount (inherits from Alice's NFT A IPAccount) + // Bob will have to pay Carl 2 MockToken as Carl is using the expensive_mint policy, which uses + // MintPaymentVerifier on mintingParam that checks for 2 MockToken payment + vm.startPrank(u.bob); // need to prank twice for .decimals() and .approve() + mockToken.approve(address(mintPaymentVerifier), 2 * 10 ** mockToken.decimals()); + vm.stopPrank(); + + licenseData = Licensing.License({ + policyId: policyIds["expensive_mint"][getIpId(u.carl, nft.c, 1)], + licensorIpIds: new address[](1) + }); + licenseData.licensorIpIds[0] = getIpId(u.carl, nft.c, 1); + + // Bob mints 2 license for policy "expensive_mint" on carl's NFT C IPAccount + + mockTokenBalanceBefore[u.bob] = mockToken.balanceOf(u.bob); + mockTokenBalanceBefore[address(mintPaymentVerifier)] = mockToken.balanceOf(address(mintPaymentVerifier)); + + licenseIds[u.bob][policyIds["expensive_mint"][getIpId(u.carl, nft.c, 1)]] = licenseRegistry.mintLicense( + licenseData, + 2, + u.bob + ); + + mockTokenBalanceAfter[u.bob] = mockToken.balanceOf(u.bob); + mockTokenBalanceAfter[address(mintPaymentVerifier)] = mockToken.balanceOf(address(mintPaymentVerifier)); + + // Bob activates above license on his NFT A IPAccount, linking as child to Carl's NFT C IPAccount + + vm.prank(u.bob); + licenseRegistry.setParentPolicy( + licenseIds[u.bob][policyIds["expensive_mint"][getIpId(u.carl, nft.c, 1)]], + getIpId(u.bob, nft.a, 2), + u.bob + ); + + assertEq( + licenseRegistry.balanceOf(u.bob, licenseIds[u.bob][policyIds["expensive_mint"][getIpId(u.carl, nft.c, 1)]]), + 1, // 1 left + "not burnt" + ); + assertTrue(licenseRegistry.isParent(getIpId(u.carl, nft.c, 1), getIpId(u.bob, nft.a, 2)), "not parent"); + assertEq( + keccak256(abi.encode(licenseRegistry.policyForIpAtIndex(getIpId(u.carl, nft.c, 1), 0))), + keccak256(abi.encode(licenseRegistry.policyForIpAtIndex(getIpId(u.bob, nft.a, 2), 0))), + "policy not copied" + ); + + assertEq( + mockTokenBalanceBefore[u.bob] - mockTokenBalanceAfter[u.bob], + 2 * 10 ** mockToken.decimals(), + "Bob didn't pay Carl" + ); + assertEq( + mockTokenBalanceAfter[address(mintPaymentVerifier)] - mockTokenBalanceBefore[address(mintPaymentVerifier)], + 2 * 10 ** mockToken.decimals(), + "Carl didn't receive payment" + ); + + // address[] memory accounts = new address[](2); + // accounts[0] = ipAccount1; + // accounts[1] = ipAccount2; + + // uint32[] memory initAllocations = new uint32[](2); + // initAllocations[0] = 100; + // initAllocations[1] = 900; + + // bytes memory data = abi.encode(accounts, initAllocations, uint32(0), address(0)); + + // royaltyModule.setRoyaltyPolicy(ipAccount3, address(royaltyPolicyLS), data); + } +} diff --git a/test/foundry/integration/flows/emergence-universe/EmergenceUniverse.t.sol b/test/foundry/integration/flows/emergence-universe/EmergenceUniverse.t.sol new file mode 100644 index 000000000..e1f8d6a02 --- /dev/null +++ b/test/foundry/integration/flows/emergence-universe/EmergenceUniverse.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.23; + +import { Test } from "forge-std/Test.sol"; + +import { IERC6551Account } from "lib/reference/src/interfaces/IERC6551Account.sol"; + +import { IIPAccount } from "contracts/interfaces/IIPAccount.sol"; +import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; +import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; +import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; +import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; +import { MockModule } from "test/foundry/mocks/MockModule.sol"; +import { Users, UsersLib } from "test/foundry/utils/Users.sol"; + +contract Integration_Flow_EmergenceUniverse_Test is Test { + Users internal u; + + function setUp() public { + u = UsersLib.createMockUsers(vm); + } + + function test_IntegrationFlow_EmergenceUniverse() public { + + } +} diff --git a/test/foundry/integration/flows/emergence-universe/README.md b/test/foundry/integration/flows/emergence-universe/README.md new file mode 100644 index 000000000..73d0fe023 --- /dev/null +++ b/test/foundry/integration/flows/emergence-universe/README.md @@ -0,0 +1,10 @@ +Bob registers Skadi IPA via Emergence Policy +We can technically see this being an example of lazy deployment. For example, The resolver shows the Skadi image and other important metadata, but since nothing is being done with Skadi yet, the IPAccount is not yet deployed. (Optional) +Emergence sets some restrictions on what Skadi can do (lets say for example, NO commercial licensing EVER) +At this point, the IPAccount needs to be created in order to set restrictions/access control. +Bob registers Skadi IPA to Magma Policy. How do we know if it is compatible? What happens if it is? What happens if it isn’t? Use some example terms so we can flow through the terms part of the protocol. +Now Bob creates an LNFT of Skadi that Alice obtains, that LNFT stipulates some additional restrictions on top of Emergence’s. Say, anyone with the LNFT can create a derivative, but that derivative cannot allow other derivatives, aka the license tree needs to stop with that derivative and no further ones. How is this additional policy stored and distinguished from the EU policy? +Alice uses that LNFT to create Evil Skadi IPA, a derivative of Skadi IPA. How does this work? +Evil Skadi inherits the restrictions set by a) Emergence b) Magma (? or no?) and c) any additional restrictions/terms Bob has slapped onto the LNFT, such as the restriction from derivatives being made of Evil Skadi. +Evil Skadi generates revenue by selling collectible copies. How does this flow back to both Alice and Bob? +Evil Skadi is copied by Evil Alice without permission. People mistakenly think this is the authentic Evil Skadi made by normal Alice. Evil Skadi generated revenue. How does Evil Skadi get disputed? What happens to the revenue? \ No newline at end of file diff --git a/test/foundry/mocks/MockAccessController.sol b/test/foundry/mocks/MockAccessController.sol index d8a74371c..a5a1b8cbf 100644 --- a/test/foundry/mocks/MockAccessController.sol +++ b/test/foundry/mocks/MockAccessController.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; import "contracts/interfaces/IAccessController.sol"; import "contracts/interfaces/IIPAccount.sol"; diff --git a/test/foundry/mocks/MockERC20.sol b/test/foundry/mocks/MockERC20.sol new file mode 100644 index 000000000..b3dca4fff --- /dev/null +++ b/test/foundry/mocks/MockERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.23; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor() ERC20("MockERC20", "MERC20") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function burn(address from, uint256 amount) external { + _burn(from, amount); + } +} diff --git a/test/foundry/mocks/MockERC721.sol b/test/foundry/mocks/MockERC721.sol index d9e9c5ca3..22abc065c 100644 --- a/test/foundry/mocks/MockERC721.sol +++ b/test/foundry/mocks/MockERC721.sol @@ -4,10 +4,32 @@ pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MockERC721 is ERC721 { - constructor() ERC721("MockERC721", "M721") {} + uint256 private _counter; - function mint(address to, uint256 tokenId) external returns(uint256) { + constructor() ERC721("MockERC721", "M721") { + _counter = 0; + } + + function mint(address to) public returns (uint256 tokenId) { + tokenId = ++_counter; + _safeMint(to, tokenId); + return tokenId; + } + + function mintId(address to, uint256 tokenId) public returns (uint256) { _safeMint(to, tokenId); return tokenId; } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public override { + _transfer(from, to, tokenId); + } } diff --git a/test/foundry/mocks/MockModule.sol b/test/foundry/mocks/MockModule.sol index 4785cd9de..42caa3e7a 100644 --- a/test/foundry/mocks/MockModule.sol +++ b/test/foundry/mocks/MockModule.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; +pragma solidity ^0.8.23; import "contracts/interfaces/modules/base/IModule.sol"; import "contracts/interfaces/registries/IIPAccountRegistry.sol"; diff --git a/test/foundry/mocks/MockUSDC.sol b/test/foundry/mocks/MockUSDC.sol new file mode 100644 index 000000000..0a20e3f6e --- /dev/null +++ b/test/foundry/mocks/MockUSDC.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.23; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockUSDC is ERC20 { + constructor() ERC20("MockUSDC", "MUSDC") {} + + function decimals() public view virtual override returns (uint8) { + return 6; + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function burn(address from, uint256 amount) external { + _burn(from, amount); + } +} diff --git a/test/foundry/IPAccountRegistry.t.sol b/test/foundry/registries/IPAccountRegistry.t.sol similarity index 64% rename from test/foundry/IPAccountRegistry.t.sol rename to test/foundry/registries/IPAccountRegistry.t.sol index 242e8ed94..fa09ac4c9 100644 --- a/test/foundry/IPAccountRegistry.t.sol +++ b/test/foundry/registries/IPAccountRegistry.t.sol @@ -1,21 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; -import "contracts/registries/IPAccountRegistry.sol"; -import "contracts/IPAccountImpl.sol"; import { ERC6551Registry } from "lib/reference/src/ERC6551Registry.sol"; -import "test/foundry/mocks/MockAccessController.sol"; + +import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; +import { IPAccountChecker } from "contracts/lib/registries/IPAccountChecker.sol"; +import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; + +import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; contract RegistryTest is Test { + using IPAccountChecker for IPAccountRegistry; + IPAccountRegistry public registry; IPAccountImpl public implementation; ERC6551Registry public erc6551Registry; MockAccessController public accessController; - uint256 chainId; - address tokenAddress; - uint256 tokenId; + uint256 internal chainId; + address internal tokenAddress; + uint256 internal tokenId; function setUp() public { implementation = new IPAccountImpl(); @@ -29,17 +34,9 @@ contract RegistryTest is Test { function test_IPAccountRegistry_registerIpAccount() public { registry = new IPAccountRegistry(address(erc6551Registry), address(accessController), address(implementation)); address ipAccountAddr; - ipAccountAddr = registry.registerIpAccount( - chainId, - tokenAddress, - tokenId - ); - - address registryComputedAddress = registry.ipAccount( - chainId, - tokenAddress, - tokenId - ); + ipAccountAddr = registry.registerIpAccount(chainId, tokenAddress, tokenId); + + address registryComputedAddress = registry.ipAccount(chainId, tokenAddress, tokenId); assertEq(ipAccountAddr, registryComputedAddress); IPAccountImpl ipAccount = IPAccountImpl(payable(ipAccountAddr)); @@ -48,16 +45,14 @@ contract RegistryTest is Test { assertEq(chainId_, chainId); assertEq(tokenAddress_, tokenAddress); assertEq(tokenId_, tokenId); + + assertTrue(registry.isRegistered(chainId, tokenAddress, tokenId)); } function test_IPAccountRegistry_revert_createAccount_ifInitFailed() public { // expect init revert for invalid accessController address registry = new IPAccountRegistry(address(erc6551Registry), address(0), address(implementation)); vm.expectRevert("Invalid access controller"); - registry.registerIpAccount( - chainId, - tokenAddress, - tokenId - ); + registry.registerIpAccount(chainId, tokenAddress, tokenId); } } diff --git a/test/foundry/resolvers/IPMetadataResolver.t.sol b/test/foundry/resolvers/IPMetadataResolver.t.sol index 8595c880b..ed698c724 100644 --- a/test/foundry/resolvers/IPMetadataResolver.t.sol +++ b/test/foundry/resolvers/IPMetadataResolver.t.sol @@ -60,7 +60,7 @@ contract IPMetadataResolverTest is ResolverBaseTest { MockERC721 erc721 = new MockERC721(); vm.prank(alice); ipResolver = IIPMetadataResolver(_deployResolver()); - uint256 tokenId = erc721.mint(alice, 99); + uint256 tokenId = erc721.mintId(alice, 99); ipId = registry.ipId(block.chainid, address(erc721), tokenId); vm.prank(registrationModule); registry.register( diff --git a/test/foundry/utils/Users.sol b/test/foundry/utils/Users.sol new file mode 100644 index 000000000..f2e8c0476 --- /dev/null +++ b/test/foundry/utils/Users.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.23; + +import { Vm } from "forge-std/Vm.sol"; + +struct Users { + // Default admin + address payable admin; + // Random users + address payable alice; + address payable bob; + address payable carl; + // Malicious user + address payable eve; +} + +library UsersLib { + function createUser(string memory name, Vm vm) public returns (address payable user) { + user = payable(address(uint160(uint256(keccak256(abi.encodePacked(name)))))); + vm.deal(user, 100 ether); // set balance to 100 ether + vm.label(user, name); + return user; + } + + function createMockUsers(Vm vm) public returns (Users memory) { + return + Users({ + // admin: payable(address(123)), // same as AccessControlHelper.sol (123 => 0x7b) + admin: createUser("Admin", vm), + alice: createUser("Alice", vm), + bob: createUser("Bob", vm), + carl: createUser("Carl", vm), + eve: createUser("Eve", vm) + }); + } +} diff --git a/test/utils/DeployHelper.sol b/test/utils/DeployHelper.sol index 8f7d27760..7f770b48e 100644 --- a/test/utils/DeployHelper.sol +++ b/test/utils/DeployHelper.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.23; -import {Test} from "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; +import { console2 } from "forge-std/console2.sol"; -import {DisputeModule} from "../../contracts/modules/dispute-module/DisputeModule.sol"; -import {ArbitrationPolicySP} from "../../contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol"; -import {RoyaltyModule} from "../../contracts/modules/royalty-module/RoyaltyModule.sol"; -import {RoyaltyPolicyLS} from "../../contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol"; +import { DisputeModule } from "../../contracts/modules/dispute-module/DisputeModule.sol"; +import { ArbitrationPolicySP } from "../../contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol"; +import { RoyaltyModule } from "../../contracts/modules/royalty-module/RoyaltyModule.sol"; +import { RoyaltyPolicyLS } from "../../contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol"; +import { MockUSDC } from "../foundry/mocks/MockUSDC.sol"; contract DeployHelper is Test { DisputeModule public disputeModule; @@ -33,9 +35,21 @@ contract DeployHelper is Test { royaltyModule = new RoyaltyModule(); royaltyPolicyLS = new RoyaltyPolicyLS(address(royaltyModule), LIQUID_SPLIT_FACTORY, LIQUID_SPLIT_MAIN); + // if code at USDC is zero, then deploy ERC20 to that address + // and also deploy other RoyaltyModule dependencies + if (USDC.code.length == 0) { + bytes memory code = vm.getDeployedCode("MockUSDC.sol:MockUSDC"); + vm.etch(USDC, code); + vm.label(USDC, "USDC"); + // assertEq(USDC.code, code); + MockUSDC(USDC).mint(USDC_RICH, 100_000_000 ether); + } + vm.label(address(disputeModule), "disputeModule"); vm.label(address(arbitrationPolicySP), "arbitrationPolicySP"); vm.label(address(royaltyModule), "royaltyModule"); vm.label(address(royaltyPolicyLS), "royaltyPolicyLS"); + + // console2.logBytes(0xdEcd8B99b7F763e16141450DAa5EA414B7994831.code); } }