├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gas-snapshot ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yaml │ ├── feature-request.md │ ├── task.md │ └── tracking_issue.md ├── pull_request_template.md └── workflows │ ├── foundry_ci.yml │ ├── hardhat_e2e.yml │ ├── notify-slack-on-pr-merge.yml │ └── scorecard.yml ├── .gitignore ├── .gitmodules ├── .husky └── pre-push ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── CHANGELOG.md ├── GUIDELINES.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── UPGRADES.md ├── assets ├── beta-architecture.png └── license-image.gif ├── audits ├── v1.0.0 │ └── Trust_Story_v1_fix_review.pdf ├── v1.1.0 │ ├── SlowMist Audit Report - Story Protocol.pdf │ └── Trust_Story_v1_1_fix_review.pdf └── v1.3 │ ├── FuzzingLabs - STORY - Final Audit Report.pdf │ ├── Halborn_Proof of Creativity Protocol _ SSC.pdf │ └── Trust_Story_Contracts_L1&PoCv02.pdf ├── contracts ├── GroupNFT.sol ├── IPAccountImpl.sol ├── IPAccountStorage.sol ├── LicenseToken.sol ├── access │ ├── AccessControlled.sol │ ├── AccessController.sol │ └── IPGraphACL.sol ├── interfaces │ ├── IGroupNFT.sol │ ├── IIPAccount.sol │ ├── IIPAccountStorage.sol │ ├── ILicenseToken.sol │ ├── access │ │ ├── IAccessController.sol │ │ └── IIPGraphACL.sol │ ├── modules │ │ ├── base │ │ │ ├── IHookModule.sol │ │ │ ├── IModule.sol │ │ │ └── IViewModule.sol │ │ ├── dispute │ │ │ ├── IDisputeModule.sol │ │ │ └── policies │ │ │ │ ├── IArbitrationPolicy.sol │ │ │ │ └── UMA │ │ │ │ ├── IArbitrationPolicyUMA.sol │ │ │ │ ├── IOOV3.sol │ │ │ │ └── IOOV3Callbacks.sol │ │ ├── grouping │ │ │ ├── IGroupRewardPool.sol │ │ │ └── IGroupingModule.sol │ │ ├── licensing │ │ │ ├── ILicenseTemplate.sol │ │ │ ├── ILicensingHook.sol │ │ │ ├── ILicensingModule.sol │ │ │ └── IPILicenseTemplate.sol │ │ ├── metadata │ │ │ ├── ICoreMetadataModule.sol │ │ │ └── ICoreMetadataViewModule.sol │ │ └── royalty │ │ │ ├── IRoyaltyModule.sol │ │ │ └── policies │ │ │ ├── IExternalRoyaltyPolicy.sol │ │ │ ├── IExternalRoyaltyPolicyBase.sol │ │ │ ├── IGraphAwareRoyaltyPolicy.sol │ │ │ ├── IIpRoyaltyVault.sol │ │ │ ├── IRoyaltyPolicy.sol │ │ │ └── IVaultController.sol │ ├── pause │ │ └── IProtocolPauseAdmin.sol │ └── registries │ │ ├── IGroupIPAssetRegistry.sol │ │ ├── IIPAccountRegistry.sol │ │ ├── IIPAssetRegistry.sol │ │ ├── ILicenseRegistry.sol │ │ └── IModuleRegistry.sol ├── lib │ ├── AccessPermission.sol │ ├── ArrayUtils.sol │ ├── BytesConversion.sol │ ├── Errors.sol │ ├── ExpiringOps.sol │ ├── IPAccountStorageOps.sol │ ├── Licensing.sol │ ├── MetaTx.sol │ ├── PILFlavors.sol │ ├── PILicenseTemplateErrors.sol │ ├── ProtocolAdmin.sol │ ├── ShortStringOps.sol │ ├── modules │ │ └── Module.sol │ └── registries │ │ └── IPAccountChecker.sol ├── modules │ ├── BaseModule.sol │ ├── dispute │ │ ├── DisputeModule.sol │ │ └── policies │ │ │ └── UMA │ │ │ └── ArbitrationPolicyUMA.sol │ ├── grouping │ │ ├── EvenSplitGroupPool.sol │ │ └── GroupingModule.sol │ ├── licensing │ │ ├── BaseLicenseTemplateUpgradeable.sol │ │ ├── LicensingModule.sol │ │ ├── PILTermsRenderer.sol │ │ ├── PILicenseTemplate.sol │ │ └── parameter-helpers │ │ │ └── LicensorApprovalChecker.sol │ ├── metadata │ │ ├── CoreMetadataModule.sol │ │ └── CoreMetadataViewModule.sol │ └── royalty │ │ ├── RoyaltyModule.sol │ │ └── policies │ │ ├── IpRoyaltyVault.sol │ │ ├── LAP │ │ └── RoyaltyPolicyLAP.sol │ │ ├── LRP │ │ └── RoyaltyPolicyLRP.sol │ │ └── VaultController.sol ├── pause │ ├── ProtocolPausableUpgradeable.sol │ └── ProtocolPauseAdmin.sol └── registries │ ├── GroupIPAssetRegistry.sol │ ├── IPAccountRegistry.sol │ ├── IPAssetRegistry.sol │ ├── LicenseRegistry.sol │ └── ModuleRegistry.sol ├── deploy-out ├── deployment-1315.json └── deployment-1514.json ├── docs └── DEPLOY_GUIDE.md ├── foundry.toml ├── hardhat.config.ts ├── helper-hardhat-config.ts ├── package.json ├── remappings.txt ├── script ├── foundry │ ├── deployment │ │ ├── Main.s.sol │ │ └── MockAssets.s.sol │ ├── upgrades │ │ ├── UpgradeDeployer.1.3.2.s.sol │ │ ├── UpgradeDeployer.example.s.sol │ │ ├── UpgradeTxGenerator.1.3.2.s.sol │ │ └── UpgradeTxGenerator.example.s.sol │ └── utils │ │ ├── BroadcastManager.s.sol │ │ ├── Create3Deployer.sol │ │ ├── DeployHelper.sol │ │ ├── ICreate3Deployer.sol │ │ ├── JsonBatchTxHelper.s.sol │ │ ├── JsonDeploymentHandler.s.sol │ │ ├── StringUtil.sol │ │ └── upgrades │ │ ├── DeployerUtils.sol │ │ ├── ERC7201Helper.s.sol │ │ ├── StorageLayoutCheck.s.sol │ │ ├── TxGenerator.s.sol │ │ ├── UpgradeImplHelper.sol │ │ └── UpgradedImplHelper.sol └── hardhat │ ├── deployment │ ├── 00-deploy-main.ts │ └── 01-deploy-mock.ts │ ├── pre-deployment │ └── 00-fund-accounts.ts │ ├── prepare-test.ts │ └── utils │ ├── constants.ts │ ├── deployed.ts │ ├── interfaces.ts │ ├── mock-assets.ts │ └── verify.ts ├── slither.config.json ├── tenderly.yaml ├── test ├── foundry │ ├── IPAccount.t.sol │ ├── IPAccountImpl.btt.t.sol │ ├── IPAccountImpl.tree │ ├── IPAccountMetaTx.t.sol │ ├── IPAccountStorage.t.sol │ ├── IPAccountStorageOps.t.sol │ ├── LicenseToken.t.sol │ ├── access │ │ ├── AccessControlled.t.sol │ │ ├── AccessController.t.sol │ │ ├── IPGraphACL.t.sol │ │ └── btt │ │ │ ├── AccessControlled.tree │ │ │ ├── AccessController.checkPermission.tree │ │ │ └── AccessController.setPermission.tree │ ├── integration │ │ ├── BaseIntegration.t.sol │ │ ├── big-bang │ │ │ ├── README.md │ │ │ └── SingleNftCollection.t.sol │ │ └── flows │ │ │ ├── disputes │ │ │ ├── Disputes.t.sol │ │ │ └── README.md │ │ │ ├── grouping │ │ │ └── Grouping.t.sol │ │ │ ├── licensing │ │ │ ├── LicensingIntegration.t.sol │ │ │ └── LicensingScenarios.t.sol │ │ │ └── royalty │ │ │ └── Royalty.t.sol │ ├── invariants │ │ ├── BaseInvariant.t.sol │ │ ├── DisputeModule.t.sol │ │ ├── IPAccount.t.sol │ │ ├── IPAssetRegistry.t.sol │ │ ├── IpRoyaltyVault.t.sol │ │ ├── LicenseToken.t.sol │ │ └── LicensingModule.t.sol │ ├── mocks │ │ ├── CustomModuleType.sol │ │ ├── MockIPGraph.sol │ │ ├── MockProtocolPausable.sol │ │ ├── MockTokenGatedHook.sol │ │ ├── dispute │ │ │ ├── MockArbitrationPolicy.sol │ │ │ └── MockIpAssetRegistry.sol │ │ ├── grouping │ │ │ └── MockEvenSplitGroupPool.sol │ │ ├── module │ │ │ ├── LicenseRegistryHarness.sol │ │ │ ├── MockAccessControlledModule.sol │ │ │ ├── MockAccessControllerV2.sol │ │ │ ├── MockAllMetadataViewModule.sol │ │ │ ├── MockCoreMetadataViewModule.sol │ │ │ ├── MockIpRoyaltyVaultV2.sol │ │ │ ├── MockLicenseTemplate.sol │ │ │ ├── MockLicensingHook.sol │ │ │ ├── MockMetaTxModule.sol │ │ │ ├── MockMetadataModule.sol │ │ │ ├── MockModule.sol │ │ │ └── MockOrchestratorModule.sol │ │ ├── policy │ │ │ ├── MockExternalRoyaltyPolicy1.sol │ │ │ ├── MockExternalRoyaltyPolicy2.sol │ │ │ ├── MockExternalRoyaltyPolicy3.sol │ │ │ └── MockRoyaltyPolicyLAP.sol │ │ └── token │ │ │ ├── MockERC1155.sol │ │ │ ├── MockERC20.sol │ │ │ ├── MockERC721.sol │ │ │ ├── MockERC721WithoutMetadata.sol │ │ │ └── SUSD.sol │ ├── modules │ │ ├── ModuleBase.t.sol │ │ ├── dispute │ │ │ ├── DisputeModule.t.sol │ │ │ ├── btt │ │ │ │ ├── DisputeModule.actions.tree │ │ │ │ └── DisputeModule.setters.tree │ │ │ └── policies │ │ │ │ └── UMA │ │ │ │ └── ArbitrationPolicyUMA.t.sol │ │ ├── grouping │ │ │ ├── EvenSplitGroupPool.t.sol │ │ │ └── GroupingModule.t.sol │ │ ├── licensing │ │ │ ├── LicensingModule.t.sol │ │ │ ├── PILicenseTemplate.t.sol │ │ │ └── README.md │ │ ├── metadata │ │ │ ├── CoreMetadataModule.t.sol │ │ │ ├── CoreMetadataViewModule.t.sol │ │ │ ├── MetadataModule.t.sol │ │ │ └── btt │ │ │ │ ├── CoreMetadataModule.tree │ │ │ │ └── CoreMetadataViewModule.tree │ │ └── royalty │ │ │ ├── IpRoyaltyVault.t.sol │ │ │ ├── LAP │ │ │ └── RoyaltyPolicyLAP.t.sol │ │ │ ├── LRP │ │ │ └── RoyaltyPolicyLRP.t.sol │ │ │ ├── RoyaltyModule.t.sol │ │ │ ├── VaultController.t.sol │ │ │ └── btt │ │ │ ├── IpRoyaltyVault.claim.tree │ │ │ ├── IpRoyaltyVault.setters.tree │ │ │ ├── IpRoyaltyVault.snapshot.tree │ │ │ ├── RoyaltyModule.callbacks.tree │ │ │ ├── RoyaltyModule.payment.tree │ │ │ ├── RoyaltyModule.setters.tree │ │ │ ├── RoyaltyPolicyLAP.initPolicy.tree │ │ │ └── RoyaltyPolicyLAP.tree │ ├── pause │ │ └── ProtocolPauseAdmin.t.sol │ ├── registries │ │ ├── IPAccountRegistry.t.sol │ │ ├── IPAssetRegistry.t.sol │ │ ├── LicenseRegistry.t.sol │ │ ├── ModuleRegistry.t.sol │ │ └── btt │ │ │ ├── IPAccountRegistry.tree │ │ │ ├── IPAssetRegistry.tree │ │ │ ├── LicenseRegistry.attachLicenseTermsToIp.tree │ │ │ ├── LicenseRegistry.registerDerivativeIp.tree │ │ │ ├── LicenseRegistry.setters.tree │ │ │ └── ModuleRegistry.tree │ ├── upgrades │ │ └── Upgrades.t.sol │ └── utils │ │ ├── BaseTest.t.sol │ │ ├── LicensingHelper.t.sol │ │ ├── TestProxyHelper.sol │ │ └── Users.t.sol └── hardhat │ ├── README.md │ └── e2e │ ├── constants.ts │ ├── dispute │ └── dispute.test.ts │ ├── grouping │ ├── group.authority.test.ts │ ├── group.ipa.test.ts │ └── group.royalty.test.ts │ ├── ipa │ ├── ipa.test.ts │ └── metadata.test.ts │ ├── ipaccount │ └── ipaccount.test.ts │ ├── license │ ├── attachLicenseTerms.test.ts │ ├── licenseTemplate.test.ts │ ├── licenseToken.test.ts │ ├── mintLicenseTokens.test.ts │ ├── registerDerivative.test.ts │ └── registerLicenseTerms.test.ts │ ├── licenseTermsTemplate.ts │ ├── permission │ └── permission.test.ts │ ├── royalty │ └── royalty.test.ts │ ├── setup.ts │ └── utils │ ├── erc20Helper.ts │ ├── licenseHelper.ts │ ├── mintNFTAndRegisterIPA.ts │ └── nftHelper.ts ├── tsconfig.json ├── upgrades └── v1.3.1-to-v1.3.2 │ ├── chainId-1315 │ ├── execute-v1.3.1-to-v1.3.2-1315.json │ ├── schedule-v1.3.1-to-v1.3.2-1315.json │ ├── transactions.json │ └── upgrade-v1.3.1-to-v1.3.2-1315.json │ └── chainId-1514 │ ├── execute-v1.3.1-to-v1.3.2-1514.json │ ├── schedule-v1.3.1-to-v1.3.2-1514.json │ ├── transactions.json │ └── upgrade-v1.3.1-to-v1.3.2-1514.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = false 12 | max_line_length = 120 13 | 14 | [*.sol] 15 | indent_size = 4 16 | 17 | [*.{js,ts}] 18 | indent_size = 2 19 | 20 | [*.{adoc,md}] 21 | max_line_length = 0 -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # MAINNET 2 | MAINNET_URL = https://eth-mainnet.g.alchemy.com/v2/1234123412341234 3 | MAINNET_PRIVATEKEY= 12341234123412341234123412341234 4 | 5 | # SEPOLIA 6 | SEPOLIA_URL = https://eth-sepolia.g.alchemy.com/v2/1234123412341234 7 | SEPOLIA_PRIVATEKEY = 12341234123412341234123412341234 8 | 9 | # TENDERLY 10 | USE_TENDERLY = false 11 | TENDERLY_URL = 12 | TENDERLY_PRIVATEKEY = 13 | 14 | # ETHERSCAN APIY KEY 15 | ETHERSCAN_API_KEY = ETHERSCANAPIKEYETHERSCANAPIKEY 16 | 17 | # TESTNET 18 | STORY_URL = http:// 19 | STORY_CHAINID = 1315 20 | STORY_PRIVATEKEY = 21 | STORY_USER1 = 22 | STORY_USER2 = 23 | STORY_ERC721 = 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .openzeppelin 3 | .eslintrc.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | plugins: ["prettier"], 4 | extends: ["eslint:recommended"], 5 | rules: { 6 | "comma-spacing": ["error", {before: false, after: true}], 7 | "prettier/prettier": "error", 8 | }, 9 | parserOptions: { 10 | ecmaVersion: 2020 11 | }, 12 | env: { 13 | es6: true, 14 | node: true, 15 | mocha: true 16 | } 17 | }; -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | CounterTest:testIncrement() (gas: 28286) 2 | CounterTest:testSetNumber(uint256) (runs: 256, μ: 27045, ~: 28367) -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @kingster-will @Spablob @LeoHChen 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Submit a bug ticket if you have an issue. If you're a user, check out the Story Protocol Discord server for faster assistance. 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | --- 8 | 9 | 10 | ## Description and context 11 | 12 | 13 | 14 | ## Steps to reproduce 15 | 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 22 | ## Experienced behavior 23 | 24 | 25 | 26 | ## Expected behavior 27 | 28 | 29 | 30 | ## Solution recommendation 31 | 32 | 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Story Protocol Official Discord 4 | url: https://discord.gg/storyprotocol 5 | about: If you're a user, this is the fastest way to get help. Do not give your wallet private key or mnemonic words to anyone. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Create a feature request item to be picked up by a contributor. 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description and context 11 | 12 | 13 | 14 | 15 | 16 | ## Suggested solution 17 | 18 | 19 | ## Definition of done 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task 3 | about: Create a regular work item to be picked up by a contributor. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description and context 11 | 12 | 13 | 14 | 15 | 16 | ## Suggested solution 17 | 18 | 19 | ## Definition of done 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/tracking_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tracking issue 3 | about: Tracking issues are task lists used to better organize regular work items. 4 | title: 'Tracking issue for *ADD_PROJECT* - *ADD_COMPONENT*' 5 | labels: 'epic' 6 | assignees: '' 7 | 8 | --- 9 | 10 | This issue is for grouping *ADD_COMPONENT* related tasks that are necessary for *ADD_PROJECT*. 11 | 12 | ### Other tracking issues for the same project: 13 | 14 | 15 | 16 | - #XXXX 17 | - #XXXX 18 | - #XXXX 19 | 20 | 21 | ```[tasklist] 22 | ### Task list 23 | - [ ] XXXX 24 | - [ ] XXXX 25 | ``` 26 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Example: 4 | This pr adds user login function, includes: 5 | 6 | - 1. add user login page. 7 | - 2. ... 8 | 9 | ## Test Plan 10 | 12 | Example: 13 | - 1. Use different test accounts for login tests, including correct user names and passwords, and incorrect user names and passwords. 14 | - 2. ... 15 | 16 | ## Related Issue 17 | 18 | 19 | Example: Issue #123 20 | 21 | ## Notes 22 | 23 | 24 | - Example: Links and navigation need to be added to the front-end interface -------------------------------------------------------------------------------- /.github/workflows/foundry_ci.yml: -------------------------------------------------------------------------------- 1 | name: Foundry CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | 10 | # Add a timestamp to the build 11 | Timestamp: 12 | uses: storyprotocol/gha-workflows/.github/workflows/reusable-timestamp.yml@main 13 | 14 | foundry-test: 15 | strategy: 16 | fail-fast: true 17 | name: Foundry Unit Test 18 | runs-on: ubuntu-latest 19 | needs: [Timestamp] 20 | steps: 21 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 22 | with: 23 | submodules: recursive 24 | fetch-depth: 0 25 | 26 | - name: Run install 27 | uses: borales/actions-yarn@v4 28 | with: 29 | cmd: install # will run `yarn install` command 30 | 31 | - name: Install Foundry 32 | uses: foundry-rs/foundry-toolchain@v1 33 | with: 34 | version: nightly 35 | 36 | # first, build contracts excluding the tests and scripts. Check contract sizes in this step. 37 | - name: Run Contract Size check 38 | run: | 39 | forge --version 40 | forge build --force --sizes --skip test --skip script 41 | 42 | # This step requires full build to be run first 43 | - name: Upgrade Safety test 44 | run: | 45 | forge clean && forge build --build-info 46 | # npx @openzeppelin/upgrades-core validate out/build-info 47 | 48 | - name: Run Forge tests 49 | run: | 50 | forge test -vvv --no-match-test "invariant*" 51 | id: forge-test 52 | 53 | - name: Run solhint 54 | run: npx solhint contracts/**/*.sol 55 | -------------------------------------------------------------------------------- /.github/workflows/notify-slack-on-pr-merge.yml: -------------------------------------------------------------------------------- 1 | name: Slack Notification on PR Merge 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | 7 | jobs: 8 | notify: 9 | if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == github.event.repository.default_branch 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Send Slack Notification 14 | run: | 15 | curl -X POST \ 16 | -H 'Content-type: application/json' \ 17 | --data '{ 18 | "channel": "qa-messenger", 19 | "text": "---\n**PR Merged** \n*Repo:* '"${{ github.repository }}"'\n*Title:* '"${{ github.event.pull_request.title }}"'\n*Tag:* <'"${{ secrets.SLACK_YAO_MEMBER_ID }}"'> <'"${{ secrets.SLACK_BO_MEMBER_ID }}"'>\n*Link:* <'"${{ github.event.pull_request.html_url }}"'|View PR>" 20 | }' \ 21 | ${{ secrets.SLACK_QA_MESSENGER_WEBHOOK_URL }} 22 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '25 1 * * 2' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: "Checkout code" 35 | uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecard on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 49 | 50 | # Public repositories: 51 | # - Publish results to OpenSSF REST API for easy access by consumers 52 | # - Allows the repository to include the Scorecard badge. 53 | # - See https://github.com/ossf/scorecard-action#publishing-results. 54 | # For private repositories: 55 | # - `publish_results` will always be set to `false`, regardless 56 | # of the value entered here. 57 | publish_results: true 58 | 59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 60 | # format to the repository Actions tab. 61 | - name: "Upload artifact" 62 | uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 63 | with: 64 | name: SARIF file 65 | path: results.sarif 66 | retention-days: 5 67 | 68 | # Upload the results to GitHub's code scanning dashboard. 69 | - name: "Upload to code-scanning" 70 | uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 71 | with: 72 | sarif_file: results.sarif 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | artifacts/ 5 | forge-cache/ 6 | 7 | # Ignores development broadcast logs 8 | # !/broadcast 9 | deployments 10 | broadcast 11 | !/broadcast/*/1/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | .idea/ 17 | node_modules/ 18 | 19 | .vscode 20 | .DS_Store 21 | pnpm-lock.yaml 22 | 23 | coverage 24 | 25 | # contracts 26 | abi 27 | typechain 28 | !./script/out 29 | 30 | # converage 31 | lcov.info 32 | 33 | # test 34 | mochawesome-report 35 | 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/openzeppelin-foundry-upgrades"] 2 | path = lib/openzeppelin-foundry-upgrades 3 | url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades 4 | [submodule "lib/create3-deployer"] 5 | path = lib/create3-deployer 6 | url = https://github.com/storyprotocol/create3-deployer 7 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn lint:sol:fix 5 | # recheck 6 | yarn solhint -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | out 4 | /lib 5 | assets 6 | node_modules 7 | .next 8 | .idea 9 | .github -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-solidity"], 3 | "useTabs": false, 4 | "printWidth": 120, 5 | "trailingComma": "es5", 6 | "tabWidth": 4, 7 | "semi": false, 8 | "singleQuote": false, 9 | "bracketSpacing": true, 10 | "overrides": [ 11 | { 12 | "files": ["*.ts", "*.js"], 13 | "options": { 14 | "tabWidth": 2 15 | } 16 | }, 17 | { 18 | "files": "*.sol", 19 | "options": { 20 | "singleQuote": false 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | norpc: true, 3 | testCommand: 'npm test', 4 | compileCommand: 'npm run compile', 5 | skipFiles: ['mocks'], 6 | providerOptions: { 7 | default_balance_ether: '10000000000000000000000000', 8 | }, 9 | mocha: { 10 | fgrep: '[skip-on-coverage]', 11 | invert: true, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "code-complexity": ["error", 9], 6 | "compiler-version": ["error", ">=0.8.23"], 7 | "const-name-snakecase": "off", 8 | "no-empty-blocks": "off", 9 | "constructor-syntax": "error", 10 | "func-visibility": ["error", { "ignoreConstructors": true }], 11 | "max-line-length": ["error", 120], 12 | "not-rely-on-time": "off", 13 | "reason-string": ["warn", { "maxLength": 64 }], 14 | "no-unused-import": "error", 15 | "no-unused-vars": "off", 16 | "no-inline-assembly": "off", 17 | "avoid-low-level-calls": "off", 18 | "no-global-import": "error", 19 | "prettier/prettier": "error", 20 | "private-vars-leading-underscore": "off", 21 | "func-name-mixedcase": "off", 22 | "var-name-mixedcase": "off", 23 | "modifier-name-mixedcase": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | /contracts/modules/royalty/policies/oppenzeppelin -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include .env 2 | 3 | .PHONY: all test clean coverage typechain format abi 4 | 5 | all: clean install build 6 | 7 | # function: generate abi for given contract name (key) 8 | # requires contract name to match the file name 9 | define generate_abi 10 | $(eval $@_CONTRACT_NAME = $(1)) 11 | $(eval $@_CONTRACT_PATH = $(2)) 12 | forge inspect --optimize --optimizer-runs 20000 contracts/${$@_CONTRACT_PATH}/${$@_CONTRACT_NAME}.sol:${$@_CONTRACT_NAME} abi > abi/${$@_CONTRACT_NAME}.json 13 | endef 14 | 15 | # Clean the repo 16 | forge-clean :; forge clean 17 | clean :; npx hardhat clean 18 | 19 | # Remove modules 20 | forge-remove :; rm -rf .gitmodules && rm -rf .git/modules/* && rm -rf lib && touch .gitmodules && git add . && git commit -m "modules" 21 | 22 | install : 23 | yarn install 24 | 25 | # Update Dependencies 26 | forge-update :; forge update 27 | 28 | forge-build :; forge build 29 | build :; npx hardhat compile 30 | 31 | test :; forge test --ffi 32 | 33 | snapshot :; forge snapshot 34 | 35 | slither :; slither ./contracts 36 | 37 | # glob doesn't work for nested folders, so we do it manually 38 | format: 39 | npx prettier --write contracts 40 | 41 | # generate forge coverage on pinned mainnet fork 42 | # process lcov file, ignore test, script, and contracts/mocks folders 43 | # generate html report from lcov.info (ignore "line ... has branchcov but no linecov data" error) 44 | coverage: 45 | mkdir -p coverage 46 | forge coverage --report lcov --no-match-path "test/foundry/invariants/*" 47 | lcov --remove lcov.info -o coverage/lcov.info 'test/*' 'script/*' --rc lcov_branch_coverage=1 48 | genhtml coverage/lcov.info -o coverage --rc lcov_branch_coverage=1 49 | 50 | abi: 51 | rm -rf abi 52 | mkdir -p abi 53 | @$(call generate_abi,"IPAccountImpl",".") 54 | @$(call generate_abi,"LicenseToken",".") 55 | @$(call generate_abi,"AccessController","./access") 56 | @$(call generate_abi,"DisputeModule","./modules/dispute") 57 | @$(call generate_abi,"LicensingModule","./modules/licensing") 58 | @$(call generate_abi,"PILicenseTemplate","./modules/licensing") 59 | @$(call generate_abi,"CoreMetadataModule","./modules/metadata") 60 | @$(call generate_abi,"CoreMetadataViewModule","./modules/metadata") 61 | @$(call generate_abi,"GroupingModule","./modules/grouping") 62 | @$(call generate_abi,"RoyaltyModule","./modules/royalty") 63 | @$(call generate_abi,"IpRoyaltyVault","./modules/royalty/policies") 64 | @$(call generate_abi,"RoyaltyPolicyLAP","./modules/royalty/policies/LAP") 65 | @$(call generate_abi,"RoyaltyPolicyLRP","./modules/royalty/policies/LRP") 66 | @$(call generate_abi,"IPAssetRegistry","./registries") 67 | @$(call generate_abi,"LicenseRegistry","./registries") 68 | @$(call generate_abi,"ModuleRegistry","./registries") 69 | 70 | typechain :; npx hardhat typechain 71 | 72 | # solhint should be installed globally 73 | lint :; npx solhint contracts/**/*.sol 74 | 75 | anvil :; anvil -m 'test test test test test test test test test test test junk' 76 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | The security of Story is critical. If you discover any security vulnerabilities, we appreciate your help in responsibly disclosing them to us. 4 | 5 | ## Reporting a Vulnerability 6 | 7 | **Please do not file a public ticket** mentioning the vulnerability. 8 | 9 | We are in the process of setting up a bug bounty program. This document will be updated when ready, and the program will be announced on our channels. 10 | 11 | We recommend to wait for the program to be ready for reporting, but if you find a vulnerability that will put the network at risk, please send an email to **security@piplabs.xyz**. We kindly request that you provide us with the following details: 12 | 13 | - A clear description of the vulnerability and its potential impact. 14 | - Steps to reproduce the vulnerability. 15 | - Any additional information or proof of concept that can help us understand and address the issue. 16 | 17 | If applicable, rewards will be provided through the bug bounty program when ready. 18 | 19 | ## Audit Reports, Known Issues and Ongoing Auditing Contest 20 | 21 | There is a series of known issues reported by our our multiple auditors. Please [review our audit reports](./audits/) to make sure you are not reporting a duplicate. 22 | 23 | Folders: 24 | 25 | - geth: audits of the original geth codebase 26 | - story: Story network audits (scope includes Story Geth, Story Consensus Client and Cosmos fork, please refer to the relevant issues for this repository) 27 | 28 | Story has undergone a public [audit competition by Cantina](https://cantina.xyz/competitions/0561defa-eeb2-4a74-8884-5d7a873afa58). We will publish the report as soon as the judging period is over. 29 | Please be advised that there is a high chance that your reported vulnerability can be a duplicate if you do it before we publish the report. 30 | 31 | ## Responsible Disclosure 32 | 33 | We believe in responsible disclosure and request that you refrain from publicly disclosing any vulnerabilities until we have had sufficient time to investigate and address them. We appreciate your cooperation in helping us maintain the security and integrity of our blockchain network. 34 | 35 | ## Disclaimer 36 | 37 | Please note that this document is subject to change and may be updated as our security practices evolve. We encourage you to check back regularly for any updates or changes. 38 | -------------------------------------------------------------------------------- /assets/beta-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/assets/beta-architecture.png -------------------------------------------------------------------------------- /assets/license-image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/assets/license-image.gif -------------------------------------------------------------------------------- /audits/v1.0.0/Trust_Story_v1_fix_review.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/audits/v1.0.0/Trust_Story_v1_fix_review.pdf -------------------------------------------------------------------------------- /audits/v1.1.0/SlowMist Audit Report - Story Protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/audits/v1.1.0/SlowMist Audit Report - Story Protocol.pdf -------------------------------------------------------------------------------- /audits/v1.1.0/Trust_Story_v1_1_fix_review.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/audits/v1.1.0/Trust_Story_v1_1_fix_review.pdf -------------------------------------------------------------------------------- /audits/v1.3/FuzzingLabs - STORY - Final Audit Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/audits/v1.3/FuzzingLabs - STORY - Final Audit Report.pdf -------------------------------------------------------------------------------- /audits/v1.3/Halborn_Proof of Creativity Protocol _ SSC.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/audits/v1.3/Halborn_Proof of Creativity Protocol _ SSC.pdf -------------------------------------------------------------------------------- /audits/v1.3/Trust_Story_Contracts_L1&PoCv02.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/audits/v1.3/Trust_Story_Contracts_L1&PoCv02.pdf -------------------------------------------------------------------------------- /contracts/interfaces/IGroupNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; 5 | 6 | /// @title IGroupNFT 7 | /// @notice Interface for the IP Group (ERC721) NFT collection that manages Group NFTs representing IP Group. 8 | /// Each Group NFT may represent an IP Group. 9 | /// Group NFTs are ERC721 NFTs that can be minted, transferred, but cannot be burned. 10 | interface IGroupNFT is IERC721Metadata { 11 | /// @notice Emitted when a IP Group NFT minted. 12 | /// @param minter The address of the minter of the IP Group NFT 13 | /// @param receiver The address of the receiver of the Group NFT. 14 | /// @param tokenId The ID of the minted IP Group NFT. 15 | event GroupNFTMinted(address indexed minter, address indexed receiver, uint256 indexed tokenId); 16 | 17 | /// @notice Mints a Group NFT. 18 | /// @param minter The address of the minter. 19 | /// @param receiver The address of the receiver of the minted Group NFT. 20 | /// @return groupNftId The ID of the minted Group NFT. 21 | function mintGroupNft(address minter, address receiver) external returns (uint256 groupNftId); 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/access/IIPGraphACL.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | interface IIPGraphACL { 5 | /// @notice Emitted when whitelisted address which can control allowed or disallowed to access the IPGraph contract. 6 | /// @param addr The address that was whitelisted. 7 | event WhitelistedAddress(address addr); 8 | 9 | /// @notice Emitted when whitelisted address is revoked. 10 | /// @param addr The address that was revoked. 11 | event RevokedWhitelistedAddress(address addr); 12 | 13 | /// @notice Start access to the IPGraph contract from internal contracts. 14 | function startInternalAccess() external; 15 | 16 | /// @notice End internal access to the IPGraph contract. 17 | function endInternalAccess() external; 18 | 19 | /// @notice Check if access to the IPGraph contract is from internal contract. 20 | function isInternalAccess() external view returns (bool); 21 | 22 | /// @notice Whitelist an address that can allow or disallow access to the IPGraph contract. 23 | /// @param addr The address to whitelist. 24 | function whitelistAddress(address addr) external; 25 | 26 | /// @notice Revoke whitelisted address. 27 | /// @param addr The address to revoke. 28 | function revokeWhitelistedAddress(address addr) external; 29 | 30 | /// @notice Check if an address is whitelisted. 31 | /// @param addr The address to check. 32 | function isWhitelisted(address addr) external view returns (bool); 33 | } 34 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/base/IHookModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IModule } from "./IModule.sol"; 5 | 6 | /// @notice Hook Module Interface 7 | interface IHookModule is IModule { 8 | /// @notice Verify if the caller can pass the hook 9 | /// @param ipId The ID of the IP asset 10 | /// @param caller The address of the caller 11 | /// @param data The arbitrary data to be verified 12 | /// @return bool Whether or not the caller has passed the hook's verification 13 | function verify(address ipId, address caller, bytes calldata data) external view returns (bool); 14 | 15 | /// @notice Validates the configuration for the hook. 16 | /// @param configData The configuration data for the hook. 17 | function validateConfig(bytes calldata configData) external view; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/base/IModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 5 | 6 | /// @notice Module Interface 7 | interface IModule is IERC165 { 8 | /// @notice Returns the string identifier associated with the module. 9 | function name() external returns (string memory); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/base/IViewModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IModule } from "./IModule.sol"; 5 | 6 | /// @notice View Module Interface 7 | /// View modules typically are read-only modules that are responsible for displaying 8 | /// IP-related data in various ways to meet different needs. For instance, 9 | /// they can display simple/base/core metadata, book specific metadata, license details metadata, 10 | /// or even IP graph data for the same IPAccount using different View Modules. 11 | /// This module offers flexibility in selecting which data to display and how to present it. 12 | /// @dev View Module can read data from IPAccount and from multiple namespaces to combine data for display. 13 | interface IViewModule is IModule { 14 | /// @notice check whether the view module is supported for the given IP account 15 | function isSupported(address ipAccount) external returns (bool); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title Arbitration Policy Interface 5 | interface IArbitrationPolicy { 6 | /// @notice Executes custom logic on raising dispute 7 | /// @dev Enforced to be only callable by the DisputeModule 8 | /// @param caller Address of the caller 9 | /// @param disputeInitiator Address of the dispute initiator 10 | /// @param targetIpId The ipId that is the target of the dispute 11 | /// @param disputeEvidenceHash The hash pointing to the dispute evidence 12 | /// @param targetTag The target tag of the dispute 13 | /// @param disputeId The dispute id 14 | /// @param data The arbitrary data used to raise the dispute 15 | function onRaiseDispute( 16 | address caller, 17 | address disputeInitiator, 18 | address targetIpId, 19 | bytes32 disputeEvidenceHash, 20 | bytes32 targetTag, 21 | uint256 disputeId, 22 | bytes calldata data 23 | ) external; 24 | 25 | /// @notice Executes custom logic on disputing judgement 26 | /// @dev Enforced to be only callable by the DisputeModule 27 | /// @param disputeId The dispute id 28 | /// @param decision The decision of the dispute 29 | /// @param data The arbitrary data used to set the dispute judgement 30 | function onDisputeJudgement(uint256 disputeId, bool decision, bytes calldata data) external; 31 | 32 | /// @notice Executes custom logic on disputing cancel 33 | /// @dev Enforced to be only callable by the DisputeModule 34 | /// @param caller Address of the caller 35 | /// @param disputeId The dispute id 36 | /// @param data The arbitrary data used to cancel the dispute 37 | function onDisputeCancel(address caller, uint256 disputeId, bytes calldata data) external; 38 | 39 | /// @notice Executes custom logic on resolving dispute 40 | /// @dev Enforced to be only callable by the DisputeModule 41 | /// @param caller Address of the caller 42 | /// @param disputeId The dispute id 43 | /// @param data The arbitrary data used to resolve the dispute 44 | function onResolveDispute(address caller, uint256 disputeId, bytes calldata data) external; 45 | } 46 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/dispute/policies/UMA/IOOV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | /// @title IOOV3 Interface 7 | interface IOOV3 { 8 | struct EscalationManagerSettings { 9 | bool arbitrateViaEscalationManager; 10 | bool discardOracle; 11 | bool validateDisputers; 12 | address assertingCaller; 13 | address escalationManager; 14 | } 15 | 16 | struct Assertion { 17 | EscalationManagerSettings escalationManagerSettings; 18 | address asserter; 19 | uint64 assertionTime; 20 | bool settled; 21 | IERC20 currency; 22 | uint64 expirationTime; 23 | bool settlementResolution; 24 | bytes32 domainId; 25 | bytes32 identifier; 26 | uint256 bond; 27 | address callbackRecipient; 28 | address disputer; 29 | } 30 | 31 | function assertTruth( 32 | bytes memory claim, 33 | address asserter, 34 | address callbackRecipient, 35 | address escalationManager, 36 | uint64 liveness, 37 | IERC20 currency, 38 | uint256 bond, 39 | bytes32 identifier, 40 | bytes32 domainId 41 | ) external returns (bytes32 assertionId); 42 | 43 | function disputeAssertion(bytes32 assertionId, address disputer) external; 44 | 45 | function settleAssertion(bytes32 assertionId) external; 46 | 47 | function getAssertion(bytes32 assertionId) external view returns (Assertion memory); 48 | 49 | function stampAssertion(bytes32 assertionId) external view returns (bytes memory); 50 | 51 | function burnedBondPercentage() external view returns (uint256); 52 | 53 | function getMinimumBond(address currency) external view returns (uint256); 54 | } 55 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/dispute/policies/UMA/IOOV3Callbacks.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title IOOV3 Callbacks Interface 5 | interface IOOV3Callbacks { 6 | function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external; 7 | 8 | function assertionDisputedCallback(bytes32 assertionId) external; 9 | } 10 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/grouping/IGroupRewardPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title IGroupingPolicy 5 | /// @notice Interface for grouping policies 6 | interface IGroupRewardPool { 7 | /// @notice Distributes rewards to the given IP accounts in pool 8 | /// @param groupId The group ID 9 | /// @param token The reward tokens 10 | /// @param ipIds The IP IDs 11 | function distributeRewards( 12 | address groupId, 13 | address token, 14 | address[] calldata ipIds 15 | ) external returns (uint256[] memory rewards); 16 | 17 | /// @notice Deposits reward to the group pool directly 18 | /// @param groupId The group ID 19 | /// @param token The reward token 20 | /// @param amount The amount of reward 21 | function depositReward(address groupId, address token, uint256 amount) external; 22 | 23 | /// @notice Adds an IP to the group pool 24 | /// @param groupId The group ID 25 | /// @param ipId The IP ID 26 | /// @param minimumGroupRewardShare The minimum group reward share the IP expects to be added to the group 27 | /// @return totalGroupRewardShare The total group reward share after adding the IP 28 | function addIp( 29 | address groupId, 30 | address ipId, 31 | uint256 minimumGroupRewardShare 32 | ) external returns (uint256 totalGroupRewardShare); 33 | 34 | /// @notice Removes an IP from the group pool 35 | /// @param groupId The group ID 36 | /// @param ipId The IP ID 37 | function removeIp(address groupId, address ipId) external; 38 | 39 | /// @notice Returns the available reward for each IP in the group 40 | /// @param groupId The group ID 41 | /// @param token The reward token 42 | /// @param ipIds The IP IDs 43 | /// @return The rewards for each IP 44 | function getAvailableReward( 45 | address groupId, 46 | address token, 47 | address[] calldata ipIds 48 | ) external view returns (uint256[] memory); 49 | } 50 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/metadata/ICoreMetadataModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IModule } from "../../../../contracts/interfaces/modules/base/IModule.sol"; 5 | 6 | /// @title CoreMetadataModule 7 | /// @notice Manages the core metadata for IP assets within the Story Protocol. 8 | /// @dev This contract allows setting and updating core metadata attributes for IP assets. 9 | interface ICoreMetadataModule is IModule { 10 | /// @notice Emitted when the nftTokenURI for an IP asset is set. 11 | event NFTTokenURISet(address indexed ipId, string nftTokenURI, bytes32 nftMetadataHash); 12 | 13 | /// @notice Emitted when the metadataURI for an IP asset is set. 14 | event MetadataURISet(address indexed ipId, string metadataURI, bytes32 metadataHash); 15 | 16 | /// @notice Emitted when all metadata for an IP asset are frozen. 17 | event MetadataFrozen(address indexed ipId); 18 | 19 | /// @notice Update the nftTokenURI for an IP asset, 20 | /// by retrieve the latest TokenURI from IP NFT to which the IP Asset bound. 21 | /// @dev Will revert if IP asset's metadata is frozen. 22 | /// @param ipId The address of the IP asset. 23 | /// @param nftMetadataHash A bytes32 hash representing the metadata of the NFT. 24 | /// This metadata is associated with the IP Asset and is accessible via the NFT's TokenURI. 25 | /// Use bytes32(0) to indicate that the metadata is not available. 26 | function updateNftTokenURI(address ipId, bytes32 nftMetadataHash) external; 27 | 28 | /// @notice Sets the metadataURI for an IP asset. 29 | /// @dev Will revert if IP asset's metadata is frozen. 30 | /// @param ipId The address of the IP asset. 31 | /// @param metadataURI The metadataURI to set for the IP asset. 32 | /// @param metadataHash The hash of metadata at metadataURI. 33 | /// Use bytes32(0) to indicate that the metadata is not available. 34 | function setMetadataURI(address ipId, string memory metadataURI, bytes32 metadataHash) external; 35 | 36 | /// @notice Sets all core metadata for an IP asset. 37 | /// @dev Will revert if IP asset's metadata is frozen. 38 | /// @param ipId The address of the IP asset. 39 | /// @param metadataURI The metadataURI to set for the IP asset. 40 | /// @param metadataHash The hash of metadata at metadataURI. 41 | /// Use bytes32(0) to indicate that the metadata is not available. 42 | /// @param nftMetadataHash A bytes32 hash representing the metadata of the NFT. 43 | /// This metadata is associated with the IP Asset and is accessible via the NFT's TokenURI. 44 | /// Use bytes32(0) to indicate that the metadata is not available. 45 | function setAll(address ipId, string memory metadataURI, bytes32 metadataHash, bytes32 nftMetadataHash) external; 46 | 47 | /// @notice make all metadata of the IP Asset immutable. 48 | /// @param ipId The address of the IP asset. 49 | function freezeMetadata(address ipId) external; 50 | 51 | /// @notice Check if the metadata of the IP Asset is immutable. 52 | /// @param ipId The address of the IP asset. 53 | function isMetadataFrozen(address ipId) external view returns (bool); 54 | } 55 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/royalty/policies/IExternalRoyaltyPolicy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 5 | import { IExternalRoyaltyPolicyBase } from "./IExternalRoyaltyPolicyBase.sol"; 6 | 7 | /// @title IExternalRoyaltyPolicy interface 8 | interface IExternalRoyaltyPolicy is IExternalRoyaltyPolicyBase, IERC165 {} 9 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/royalty/policies/IExternalRoyaltyPolicyBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title IExternalRoyaltyPolicyBase interface 5 | interface IExternalRoyaltyPolicyBase { 6 | /// @notice Returns the amount of royalty tokens required to link a child to a given IP asset 7 | /// @param ipId The ipId of the IP asset 8 | /// @param licensePercent The percentage of the license 9 | /// @return The amount of royalty tokens required to link a child to a given IP asset 10 | function getPolicyRtsRequiredToLink(address ipId, uint32 licensePercent) external view returns (uint32); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/royalty/policies/IGraphAwareRoyaltyPolicy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IRoyaltyPolicy } from "../../../../interfaces/modules/royalty/policies/IRoyaltyPolicy.sol"; 5 | 6 | /// @title IGraphAwareRoyaltyPolicy interface 7 | interface IGraphAwareRoyaltyPolicy is IRoyaltyPolicy { 8 | /// @notice Event emitted when revenue tokens are transferred to a vault from a royalty policy 9 | /// @param ipId The ipId of the IP asset 10 | /// @param ancestorIpId The ancestor ipId of the IP asset whose vault will receive revenue tokens 11 | /// @param token The address of the token that is transferred 12 | /// @param amount The amount of tokens transferred 13 | event RevenueTransferredToVault(address ipId, address ancestorIpId, address token, uint256 amount); 14 | 15 | /// @notice Transfers to vault an amount of revenue tokens 16 | /// @param ipId The ipId of the IP asset 17 | /// @param ancestorIpId The ancestor ipId of the IP asset 18 | /// @param token The token address to transfer 19 | /// @return The amount of revenue tokens transferred 20 | function transferToVault(address ipId, address ancestorIpId, address token) external returns (uint256); 21 | 22 | /// @notice Returns the royalty percentage between an IP asset and a given ancestor 23 | /// @param ipId The ipId to get the royalty for 24 | /// @param ancestorIpId The ancestor ipId to get the royalty for 25 | /// @return The royalty percentage between an IP asset and a given ancestor 26 | function getPolicyRoyalty(address ipId, address ancestorIpId) external returns (uint32); 27 | 28 | /// @notice Returns the total lifetime revenue tokens transferred to an ancestor's vault from a given IP asset 29 | /// @param ipId The ipId of the IP asset 30 | /// @param ancestorIpId The ancestor ipId of the IP asset 31 | /// @param token The token address to transfer 32 | /// @return The total lifetime revenue tokens transferred to an ancestor's vault from a given IP asset 33 | function getTransferredTokens(address ipId, address ancestorIpId, address token) external view returns (uint256); 34 | } 35 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/royalty/policies/IRoyaltyPolicy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IExternalRoyaltyPolicyBase } from "./IExternalRoyaltyPolicyBase.sol"; 5 | 6 | /// @title RoyaltyPolicy interface 7 | interface IRoyaltyPolicy is IExternalRoyaltyPolicyBase { 8 | /// @notice Executes royalty related logic on minting a license 9 | /// @dev Enforced to be only callable by RoyaltyModule 10 | /// @param ipId The ipId whose license is being minted (licensor) 11 | /// @param licensePercent The license percentage of the license being minted 12 | /// @param externalData The external data custom to each the royalty policy 13 | function onLicenseMinting(address ipId, uint32 licensePercent, bytes calldata externalData) external; 14 | 15 | /// @notice Executes royalty related logic on linking to parents 16 | /// @dev Enforced to be only callable by RoyaltyModule 17 | /// @param ipId The children ipId that is being linked to parents 18 | /// @param parentIpIds The parent ipIds that the children ipId is being linked to 19 | /// @param licenseRoyaltyPolicies The royalty policies of the license 20 | /// @param licensesPercent The license percentages of the licenses being minted 21 | /// @param externalData The external data custom to each the royalty policy 22 | /// @return The royalty stack of the child ipId for a given royalty policy 23 | function onLinkToParents( 24 | address ipId, 25 | address[] calldata parentIpIds, 26 | address[] calldata licenseRoyaltyPolicies, 27 | uint32[] calldata licensesPercent, 28 | bytes calldata externalData 29 | ) external returns (uint32); 30 | 31 | /// @notice Returns the royalty stack for a given IP asset 32 | /// @param ipId The ID of the IP asset 33 | /// @return royaltyStack Sum of the royalty percentages to be paid to ancestors for a given royalty policy 34 | function getPolicyRoyaltyStack(address ipId) external view returns (uint32); 35 | 36 | /// @notice Returns the royalty policy support working with group 37 | /// @return True if the royalty policy support working with group otherwise false 38 | function isSupportGroup() external view returns (bool); 39 | } 40 | -------------------------------------------------------------------------------- /contracts/interfaces/modules/royalty/policies/IVaultController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title VaultController interface 5 | interface IVaultController { 6 | /// @dev Set the ip royalty vault beacon 7 | /// @dev Enforced to be only callable by the protocol admin in governance 8 | /// @param beacon The ip royalty vault beacon address 9 | function setIpRoyaltyVaultBeacon(address beacon) external; 10 | 11 | /// @dev Upgrades the ip royalty vault beacon 12 | /// @dev Enforced to be only callable by the upgrader admin 13 | /// @param newVault The new ip royalty vault beacon address 14 | function upgradeVaults(address newVault) external; 15 | 16 | /// @notice Returns the ip royalty vault beacon 17 | /// @return ipRoyaltyVaultBeacon The ip royalty vault beacon address 18 | function ipRoyaltyVaultBeacon() external view returns (address); 19 | } 20 | -------------------------------------------------------------------------------- /contracts/interfaces/pause/IProtocolPauseAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title ProtocolPauseAdmin 5 | /// @notice Contract that allows the pausing and unpausing of the protocol. It allows adding and removing 6 | /// pausable contracts, which are contracts that implement the `IPausable` interface. 7 | /// @dev The contract is restricted to be used only the admin role defined in the `AccessManaged` contract. 8 | /// NOTE: If a contract is upgraded to remove the `IPausable` interface, it should be removed from the list of pausables 9 | /// before the upgrade, otherwise pause() and unpause() will revert. 10 | interface IProtocolPauseAdmin { 11 | /// @notice Emitted when a pausable contract is added. 12 | event PausableAdded(address indexed pausable); 13 | /// @notice Emitted when a pausable contract is removed. 14 | event PausableRemoved(address indexed pausable); 15 | /// @notice Emitted when the protocol is paused. 16 | event ProtocolPaused(); 17 | /// @notice Emitted when the protocol is unpaused. 18 | event ProtocolUnpaused(); 19 | 20 | /// @notice Adds a pausable contract to the list of pausables. 21 | function addPausable(address pausable) external; 22 | 23 | /// @notice Removes a pausable contract from the list of pausables. 24 | function removePausable(address pausable) external; 25 | 26 | /// @notice Pauses the protocol by calling the pause() function on all pausable contracts. 27 | function pause() external; 28 | 29 | /// @notice Unpauses the protocol by calling the unpause() function on all pausable contracts. 30 | function unpause() external; 31 | 32 | /// @notice Checks if a pausable contract is registered. 33 | function isPausableRegistered(address pausable) external view returns (bool); 34 | 35 | /// @notice Returns true if all the pausable contracts are paused. 36 | function isAllProtocolPaused() external view returns (bool); 37 | 38 | /// @notice Returns the list of pausable contracts. 39 | function pausables() external view returns (address[] memory); 40 | } 41 | -------------------------------------------------------------------------------- /contracts/interfaces/registries/IIPAccountRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title Interface for IP Account Registry 5 | /// @notice This interface manages the registration and tracking of IP Accounts 6 | interface IIPAccountRegistry { 7 | /// @notice Event emitted when a new IP Account is created 8 | /// @param account The address of the new IP Account 9 | /// @param implementation The address of the IP Account implementation 10 | /// @param chainId The chain ID where the token contract was deployed 11 | /// @param tokenContract The address of the token contract associated with the IP Account 12 | /// @param tokenId The ID of the token associated with the IP Account 13 | event IPAccountRegistered( 14 | address indexed account, 15 | address indexed implementation, 16 | uint256 indexed chainId, 17 | address tokenContract, 18 | uint256 tokenId 19 | ); 20 | 21 | /// @notice Returns the IPAccount address for the given NFT token. 22 | /// @param chainId The chain ID where the IP Account is located 23 | /// @param tokenContract The address of the token contract associated with the IP Account 24 | /// @param tokenId The ID of the token associated with the IP Account 25 | /// @return ipAccountAddress The address of the IP Account associated with the given NFT token 26 | function ipAccount(uint256 chainId, address tokenContract, uint256 tokenId) external view returns (address); 27 | 28 | /// @notice Returns the IPAccount implementation address. 29 | /// @return The address of the IPAccount implementation 30 | function getIPAccountImpl() external view returns (address); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/lib/AccessPermission.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title Access Permission Library 5 | /// @notice Library for IPAccount access control permissions. 6 | /// These permissions are used by the AccessController. 7 | library AccessPermission { 8 | /// @notice ABSTAIN means not having enough information to make a decision at the current level, 9 | /// the deferred decision to up level permission. 10 | uint8 public constant ABSTAIN = 0; 11 | 12 | /// @notice ALLOW means the permission is granted to transaction signer to call the function. 13 | uint8 public constant ALLOW = 1; 14 | 15 | /// @notice DENY means the permission is denied to transaction signer to call the function. 16 | uint8 public constant DENY = 2; 17 | 18 | /// @notice This struct is used to represent permissions in batch operations within the AccessController. 19 | /// @param ipAccount The address of the IP account that grants the permission for `signer` 20 | /// @param signer The address that can call `to` on behalf of the `ipAccount` 21 | /// @param to The address that can be called by the `signer` (currently only modules can be `to`) 22 | /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` 23 | /// @param permission The permission level for the transaction (0 = ABSTAIN, 1 = ALLOW, 2 = DENY). 24 | struct Permission { 25 | address ipAccount; 26 | address signer; 27 | address to; 28 | bytes4 func; 29 | uint8 permission; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/lib/ArrayUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title Address Array Utils 5 | /// @notice Library for address array operations 6 | library ArrayUtils { 7 | /// @notice Finds the index of the first occurrence of the given element. 8 | /// @param array The input array to search 9 | /// @param element The value to find 10 | /// @return Returns (index and isIn) for the first occurrence starting from index 0 11 | function indexOf(address[] memory array, address element) internal pure returns (uint32, bool) { 12 | for (uint32 i = 0; i < array.length; i++) { 13 | if (array[i] == element) return (i, true); 14 | } 15 | return (0, false); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/lib/BytesConversion.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title Bytes Conversion 5 | /// @notice Library for bytes conversion operations 6 | library BytesConversion { 7 | /// @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type 8 | function toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { 9 | if (x == 0) { 10 | return "0"; 11 | } 12 | uint256 j = x; 13 | uint256 len; 14 | while (j != 0) { 15 | len++; 16 | j /= 10; 17 | } 18 | bytes memory bstr = new bytes(len); 19 | uint256 k = len; 20 | while (x != 0) { 21 | k = k - 1; 22 | uint8 temp = (48 + uint8(x - (x / 10) * 10)); 23 | bytes1 b1 = bytes1(temp); 24 | bstr[k] = b1; 25 | x /= 10; 26 | } 27 | return bstr; 28 | } 29 | 30 | /// @notice Returns utf8-encoded address that can be read via web3.utils.hexToUtf8 31 | function toUtf8BytesAddress(address x) internal pure returns (bytes memory) { 32 | return 33 | abi.encodePacked(toUtf8Bytes32Bottom(bytes32(bytes20(x)) >> 128), bytes8(toUtf8Bytes32Bottom(bytes20(x)))); 34 | } 35 | 36 | /// @notice Returns utf8-encoded bytes32 string that can be read via web3.utils.hexToUtf8 37 | function toUtf8Bytes(bytes32 bytesIn) internal pure returns (bytes memory) { 38 | return abi.encodePacked(toUtf8Bytes32Bottom(bytesIn >> 128), toUtf8Bytes32Bottom(bytesIn)); 39 | } 40 | 41 | /// @notice This converts the bottom half of a bytes32 input to hex 42 | function toUtf8Bytes32Bottom(bytes32 bytesIn) private pure returns (bytes32) { 43 | unchecked { 44 | uint256 x = uint256(bytesIn); 45 | 46 | // Nibble interleave 47 | x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff; 48 | x = (x | (x * 2 ** 64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff; 49 | x = (x | (x * 2 ** 32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff; 50 | x = (x | (x * 2 ** 16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff; 51 | x = (x | (x * 2 ** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff; 52 | x = (x | (x * 2 ** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f; 53 | 54 | // Hex encode 55 | uint256 h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8; 56 | uint256 i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4; 57 | uint256 j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2; 58 | x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030; 59 | 60 | // Return the result. 61 | return bytes32(x); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/lib/ExpiringOps.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | library ExpiringOps { 5 | /// @dev Get the earliest expiration time from two expiration times 6 | /// @param currentEarliestExp The current earliest expiration time 7 | /// @param anotherExp Another expiration time 8 | function getEarliestExpirationTime( 9 | uint256 currentEarliestExp, 10 | uint256 anotherExp 11 | ) internal view returns (uint256 earliestExp) { 12 | earliestExp = currentEarliestExp; 13 | if (anotherExp > 0 && (anotherExp < earliestExp || earliestExp == 0)) earliestExp = anotherExp; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/lib/Licensing.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title Licensing 5 | /// @notice Types and constants used by the licensing related contracts 6 | library Licensing { 7 | /// @notice This struct is used by IP owners to define the configuration 8 | /// when others are minting license tokens of their IP through the LicensingModule. 9 | /// When the `mintLicenseTokens` function of LicensingModule is called, the LicensingModule will read 10 | /// this configuration to determine the minting fee and execute the licensing hook if set. 11 | /// IP owners can set these configurations for each License or set the configuration for the IP 12 | /// so that the configuration applies to all licenses of the IP. 13 | /// If both the license and IP have the configuration, then the license configuration takes precedence. 14 | /// @param isSet Whether the configuration is set or not. 15 | /// @param mintingFee The minting fee to be paid when minting license tokens. 16 | /// @param licensingHook The hook contract address for the licensing module, or address(0) if none 17 | /// @param hookData The data to be used by the licensing hook. 18 | /// @param commercialRevShare The commercial revenue share percentage. 19 | /// @param disabled Whether the license is disabled or not. 20 | /// @param expectMinimumGroupRewardShare The minimum percentage of the group’s reward share 21 | /// (from 0 to 100%, represented as 100 * 10 ** 6) that can be allocated to the IP when it is added to the group. 22 | /// If the remaining reward share in the group is less than the minimumGroupRewardShare, 23 | /// the IP cannot be added to the group. 24 | /// @param expectGroupRewardPool The address of the expected group reward pool. 25 | /// The IP can only be added to a group with this specified reward pool address, 26 | /// or address(0) if the IP does not want to be added to any group. 27 | struct LicensingConfig { 28 | bool isSet; 29 | uint256 mintingFee; 30 | address licensingHook; 31 | bytes hookData; 32 | uint32 commercialRevShare; 33 | bool disabled; 34 | uint32 expectMinimumGroupRewardShare; 35 | address expectGroupRewardPool; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/lib/MetaTx.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title MetaTx 5 | /// @dev This library provides functions for handling meta transactions in the Story Protocol. 6 | library MetaTx { 7 | /// @dev Version of the EIP712 domain. 8 | string public constant EIP712_DOMAIN_VERSION = "1"; 9 | /// @dev Hash of the EIP712 domain version. 10 | bytes32 public constant EIP712_DOMAIN_VERSION_HASH = keccak256(bytes(EIP712_DOMAIN_VERSION)); 11 | /// @dev EIP712 domain type hash. 12 | bytes32 public constant EIP712_DOMAIN = 13 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); 14 | /// @dev Execute type hash. 15 | bytes32 public constant EXECUTE = 16 | keccak256("Execute(address to,uint256 value,bytes data,bytes32 nonce,uint256 deadline)"); 17 | 18 | /// @dev Structure for the Execute type. 19 | struct Execute { 20 | address to; 21 | uint256 value; 22 | bytes data; 23 | bytes32 nonce; 24 | uint256 deadline; 25 | } 26 | 27 | /// @dev Calculates the EIP712 domain separator for the current contract. 28 | /// @return The EIP712 domain separator. 29 | function calculateDomainSeparator() internal view returns (bytes32) { 30 | return calculateDomainSeparator(address(this)); 31 | } 32 | 33 | /// @dev Calculates the EIP712 domain separator for a given IP account. 34 | /// @param ipAccount The IP account for which to calculate the domain separator. 35 | /// @return The EIP712 domain separator. 36 | function calculateDomainSeparator(address ipAccount) internal view returns (bytes32) { 37 | return 38 | keccak256( 39 | abi.encode( 40 | EIP712_DOMAIN, 41 | keccak256("Story Protocol IP Account"), 42 | EIP712_DOMAIN_VERSION_HASH, 43 | block.chainid, 44 | ipAccount 45 | ) 46 | ); 47 | } 48 | 49 | /// @dev Calculates the EIP712 struct hash of an Execute. 50 | /// @param execute The Execute to hash. 51 | /// @return The EIP712 struct hash of the Execute. 52 | function getExecuteStructHash(Execute memory execute) internal pure returns (bytes32) { 53 | return 54 | keccak256( 55 | abi.encode( 56 | MetaTx.EXECUTE, 57 | execute.to, 58 | execute.value, 59 | keccak256(execute.data), 60 | execute.nonce, 61 | execute.deadline 62 | ) 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/lib/ProtocolAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | /// @title ProtocolAdmin 5 | /// @dev This library provides roles and utils to configure protocol AccessManager 6 | library ProtocolAdmin { 7 | /// @notice Protocol admin role, as it is used in AccessManager. 8 | /// Root admin role, grants all roles. 9 | uint64 public constant PROTOCOL_ADMIN_ROLE = type(uint64).min; // 0 10 | string public constant PROTOCOL_ADMIN_ROLE_LABEL = "PROTOCOL_ADMIN_ROLE"; 11 | /// @notice Public role, as it is used in AccessManager 12 | uint64 public constant PUBLIC_ROLE = type(uint64).max; // 2**64-1 13 | /// @notice Upgrader role. Grants ability to upgrade UUPS contracts. 14 | uint64 public constant UPGRADER_ROLE = 1; 15 | string public constant UPGRADER_ROLE_LABEL = "UPGRADER_ROLE"; 16 | /// @notice Pause admin role. Grants ability to pause and unpause contracts. 17 | uint64 public constant PAUSE_ADMIN_ROLE = 2; 18 | string public constant PAUSE_ADMIN_ROLE_LABEL = "PAUSE_ADMIN_ROLE"; 19 | /// @notice Guardian role. Grants ability to configure roles. 20 | uint64 public constant GUARDIAN_ROLE = 3; 21 | string public constant GUARDIAN_ROLE_LABEL = "GUARDIAN_ROLE"; 22 | } 23 | -------------------------------------------------------------------------------- /contracts/lib/ShortStringOps.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol"; 5 | import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | /// @notice Library for working with Openzeppelin's ShortString data types. 8 | library ShortStringOps { 9 | using ShortStrings for *; 10 | using Strings for *; 11 | 12 | /// @dev Convert string to bytes32 using ShortString 13 | function stringToBytes32(string memory s) internal pure returns (bytes32) { 14 | return ShortString.unwrap(s.toShortString()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/lib/modules/Module.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | // Default Module Type, all modules in this type by default 5 | string constant MODULE_TYPE_DEFAULT = "MODULE"; 6 | 7 | string constant MODULE_TYPE_HOOK = "HOOK_MODULE"; 8 | 9 | // String values for core protocol modules. 10 | 11 | string constant LICENSING_MODULE_KEY = "LICENSING_MODULE"; 12 | 13 | string constant DISPUTE_MODULE_KEY = "DISPUTE_MODULE"; 14 | 15 | string constant ROYALTY_MODULE_KEY = "ROYALTY_MODULE"; 16 | 17 | string constant TOKEN_WITHDRAWAL_MODULE_KEY = "TOKEN_MANAGEMENT_MODULE"; 18 | 19 | string constant CORE_METADATA_MODULE_KEY = "CORE_METADATA_MODULE"; 20 | 21 | string constant CORE_METADATA_VIEW_MODULE_KEY = "CORE_METADATA_VIEW_MODULE"; 22 | 23 | string constant GROUPING_MODULE_KEY = "GROUPING_MODULE"; 24 | -------------------------------------------------------------------------------- /contracts/lib/registries/IPAccountChecker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 5 | import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol"; 6 | 7 | import { IIPAssetRegistry } from "../../interfaces/registries/IIPAssetRegistry.sol"; 8 | import { IIPAccount } from "../../interfaces/IIPAccount.sol"; 9 | import { IIPAccountStorage } from "../../interfaces/IIPAccountStorage.sol"; 10 | 11 | /// @title IPAccountChecker 12 | /// @dev This library provides utility functions to check the registration and validity of IP Accounts. 13 | /// It uses the ERC165 standard for contract introspection and the IIPAccountRegistry interface 14 | /// for account registration checks. 15 | library IPAccountChecker { 16 | /// @notice Returns true if the IPAccount is registered. 17 | /// @param chainId_ The chain ID where the IP Account is located. 18 | /// @param tokenContract_ The address of the token contract associated with the IP Account. 19 | /// @param tokenId_ The ID of the token associated with the IP Account. 20 | /// @return True if the IP Account is registered, false otherwise. 21 | function isRegistered( 22 | IIPAssetRegistry ipAssetRegistry_, 23 | uint256 chainId_, 24 | address tokenContract_, 25 | uint256 tokenId_ 26 | ) internal view returns (bool) { 27 | return ipAssetRegistry_.isRegistered(ipAssetRegistry_.ipId(chainId_, tokenContract_, tokenId_)); 28 | } 29 | 30 | /// @notice Checks if the given address is a valid IP Account. 31 | /// @param ipAssetRegistry_ The IP Account registry contract. 32 | /// @param ipAccountAddress_ The address to check. 33 | /// @return True if the address is a valid IP Account, false otherwise. 34 | function isIpAccount(IIPAssetRegistry ipAssetRegistry_, address ipAccountAddress_) internal view returns (bool) { 35 | if (ipAccountAddress_ == address(0)) return false; 36 | if (ipAccountAddress_.code.length == 0) return false; 37 | if (!ERC165Checker.supportsERC165(ipAccountAddress_)) return false; 38 | if (!ERC165Checker.supportsInterface(ipAccountAddress_, type(IERC6551Account).interfaceId)) return false; 39 | if (!ERC165Checker.supportsInterface(ipAccountAddress_, type(IIPAccount).interfaceId)) return false; 40 | if (!ERC165Checker.supportsInterface(ipAccountAddress_, type(IIPAccountStorage).interfaceId)) return false; 41 | return ipAssetRegistry_.isRegistered(ipAccountAddress_); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/modules/BaseModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165, ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | import { IModule } from "../interfaces/modules/base/IModule.sol"; 6 | 7 | /// @title BaseModule 8 | /// @notice Base implementation for all modules in Story Protocol. 9 | abstract contract BaseModule is ERC165, IModule { 10 | /// @notice IERC165 interface support. 11 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 12 | return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/licensing/BaseLicenseTemplateUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | // external 5 | import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 6 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 7 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 8 | // contracts 9 | import { ILicenseTemplate } from "../../interfaces/modules/licensing/ILicenseTemplate.sol"; 10 | 11 | abstract contract BaseLicenseTemplateUpgradeable is ILicenseTemplate, ERC165, Initializable { 12 | /// @dev Storage structure for the BaseLicenseTemplateUpgradeable 13 | /// @custom:storage-location erc7201:story-protocol.BaseLicenseTemplateUpgradeable 14 | struct BaseLicenseTemplateUpgradeableStorage { 15 | string name; 16 | string metadataURI; 17 | } 18 | 19 | // keccak256(abi.encode(uint256(keccak256("story-protocol.BaseLicenseTemplateUpgradeable")) - 1)) & 20 | // ~bytes32(uint256(0xff)); 21 | bytes32 private constant BaseLicenseTemplateUpgradeableStorageLocation = 22 | 0x96c2f019b095cfe7c4d1f26aa9d2741961fe73294777688374a3299707c2fb00; 23 | 24 | /// @custom:oz-upgrades-unsafe-allow constructor 25 | constructor() { 26 | _disableInitializers(); 27 | } 28 | 29 | /// @notice initializer for this implementation contract 30 | /// @param _name The name of the license template 31 | /// @param _metadataURI The URL to the off chain metadata 32 | function __BaseLicenseTemplate_init(string memory _name, string memory _metadataURI) internal onlyInitializing { 33 | _getBaseLicenseTemplateUpgradeableStorage().name = _name; 34 | _getBaseLicenseTemplateUpgradeableStorage().metadataURI = _metadataURI; 35 | } 36 | 37 | /// @notice Returns the name of the license template 38 | function name() public view override returns (string memory) { 39 | return _getBaseLicenseTemplateUpgradeableStorage().name; 40 | } 41 | 42 | /// @notice Returns the URL to the off chain metadata 43 | function getMetadataURI() public view override returns (string memory) { 44 | return _getBaseLicenseTemplateUpgradeableStorage().metadataURI; 45 | } 46 | 47 | /// @notice IERC165 interface support. 48 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 49 | return interfaceId == type(ILicenseTemplate).interfaceId || super.supportsInterface(interfaceId); 50 | } 51 | 52 | /// @dev Returns the storage struct of BaseLicenseTemplateUpgradeable. 53 | function _getBaseLicenseTemplateUpgradeableStorage() 54 | private 55 | pure 56 | returns (BaseLicenseTemplateUpgradeableStorage storage $) 57 | { 58 | assembly { 59 | $.slot := BaseLicenseTemplateUpgradeableStorageLocation 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/modules/royalty/policies/VaultController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; 5 | 6 | import { IVaultController } from "../../../interfaces/modules/royalty/policies/IVaultController.sol"; 7 | import { ProtocolPausableUpgradeable } from "../../../pause/ProtocolPausableUpgradeable.sol"; 8 | import { Errors } from "../../../lib/Errors.sol"; 9 | 10 | /// @title Vault Controller 11 | /// @notice Abstract contract that defines the common logic for royalty policies with ip royalty vaults 12 | abstract contract VaultController is IVaultController, ProtocolPausableUpgradeable { 13 | /// @dev Storage structure for the VaultController 14 | /// @param ipRoyaltyVaultBeacon The ip royalty vault beacon address 15 | /// @custom:storage-location erc7201:story-protocol.VaultController 16 | struct VaultControllerStorage { 17 | address ipRoyaltyVaultBeacon; 18 | } 19 | 20 | // keccak256(abi.encode(uint256(keccak256("story-protocol.VaultController")) - 1)) & ~bytes32(uint256(0xff)); 21 | bytes32 private constant VaultControllerStorageLocation = 22 | 0x88cf5a7bd03e240c4fc740fb2d1a8664ec6fa4816f867d60f968080755fb1700; 23 | 24 | /// @dev Set the ip royalty vault beacon 25 | /// @dev Enforced to be only callable by the protocol admin in governance 26 | /// @param beacon The ip royalty vault beacon address 27 | function setIpRoyaltyVaultBeacon(address beacon) external restricted { 28 | if (beacon == address(0)) revert Errors.VaultController__ZeroIpRoyaltyVaultBeacon(); 29 | VaultControllerStorage storage $ = _getVaultControllerStorage(); 30 | $.ipRoyaltyVaultBeacon = beacon; 31 | } 32 | 33 | /// @dev Upgrades the ip royalty vault beacon 34 | /// @dev Enforced to be only callable by the upgrader admin 35 | /// @param newVault The new ip royalty vault beacon address 36 | function upgradeVaults(address newVault) external restricted { 37 | // UpgradeableBeacon already checks for newImplementation.bytecode.length > 0, 38 | // no need to check for zero address 39 | VaultControllerStorage storage $ = _getVaultControllerStorage(); 40 | UpgradeableBeacon($.ipRoyaltyVaultBeacon).upgradeTo(newVault); 41 | } 42 | 43 | /// @notice Returns the ip royalty vault beacon 44 | /// @return ipRoyaltyVaultBeacon The ip royalty vault beacon address 45 | function ipRoyaltyVaultBeacon() public view returns (address) { 46 | return _getVaultControllerStorage().ipRoyaltyVaultBeacon; 47 | } 48 | 49 | /// @dev Returns the storage struct of VaultController. 50 | function _getVaultControllerStorage() private pure returns (VaultControllerStorage storage $) { 51 | assembly { 52 | $.slot := VaultControllerStorageLocation 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/pause/ProtocolPausableUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | // solhint-disable-next-line max-line-length 5 | import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; 6 | import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; 7 | 8 | /// @title ProtocolPausable 9 | /// @notice Contract that allows the pausing and unpausing of the a contract 10 | abstract contract ProtocolPausableUpgradeable is PausableUpgradeable, AccessManagedUpgradeable { 11 | /// @notice Initializes the ProtocolPausable contract 12 | /// @param accessManager The address of the access manager 13 | function __ProtocolPausable_init(address accessManager) public initializer { 14 | __AccessManaged_init(accessManager); 15 | __Pausable_init(); 16 | } 17 | 18 | /// @notice sets paused state 19 | function pause() external restricted { 20 | _pause(); 21 | } 22 | 23 | /// @notice unsets unpaused state 24 | function unpause() external restricted { 25 | _unpause(); 26 | } 27 | 28 | function paused() public view override returns (bool) { 29 | return super.paused(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /deploy-out/deployment-1315.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "AccessController": "0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a", 4 | "ArbitrationPolicyUMA": "0xfFD98c3877B8789124f02C7E8239A4b0Ef11E936", 5 | "CoreMetadataModule": "0x6E81a25C99C6e8430aeC7353325EB138aFE5DC16", 6 | "CoreMetadataViewModule": "0xB3F88038A983CeA5753E11D144228Ebb5eACdE20", 7 | "DisputeModule": "0x9b7A9c70AFF961C799110954fc06F3093aeb94C5", 8 | "EvenSplitGroupPool": "0xf96f2c30b41Cb6e0290de43C8528ae83d4f33F89", 9 | "GroupNFT": "0x4709798FeA84C84ae2475fF0c25344115eE1529f", 10 | "GroupingModule": "0x69D3a7aa9edb72Bc226E745A7cCdd50D947b69Ac", 11 | "IPAccountImplBeacon": "0x9825cc7A398D9C3dDD66232A8Ec76d5b05422581", 12 | "IPAccountImplBeaconProxy": "0x00b800138e4D82D1eea48b414d2a2A8Aee9A33b1", 13 | "IPAccountImplCode": "0x6ccAd5718a27fB6a09EAdb737D889A2007838b77", 14 | "IPAssetRegistry": "0x77319B4031e6eF1250907aa00018B8B1c67a244b", 15 | "IPGraphACL": "0x1640A22a8A086747cD377b73954545e2Dfcc9Cad", 16 | "IpRoyaltyVaultBeacon": "0x6928ba25Aa5c410dd855dFE7e95713d83e402AA6", 17 | "IpRoyaltyVaultImpl": "0x73e2D097F71e5103824abB6562362106A8955AEc", 18 | "LicenseRegistry": "0x529a750E02d8E2f15649c13D69a465286a780e24", 19 | "LicenseToken": "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC", 20 | "LicensingModule": "0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f", 21 | "ModuleRegistry": "0x022DBAAeA5D8fB31a0Ad793335e39Ced5D631fa5", 22 | "PILicenseTemplate": "0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316", 23 | "ProtocolAccessManager": "0xFdece7b8a2f55ceC33b53fd28936B4B1e3153d53", 24 | "ProtocolPauseAdmin": "0xdd661f55128A80437A0c0BDA6E13F214A3B2EB24", 25 | "RoyaltyModule": "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086", 26 | "RoyaltyPolicyLAP": "0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E", 27 | "RoyaltyPolicyLRP": "0x9156e603C949481883B1d3355c6f1132D191fC41" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /deploy-out/deployment-1514.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "AccessController": "0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a", 4 | "ArbitrationPolicyUMA": "0xfFD98c3877B8789124f02C7E8239A4b0Ef11E936", 5 | "CoreMetadataModule": "0x6E81a25C99C6e8430aeC7353325EB138aFE5DC16", 6 | "CoreMetadataViewModule": "0xB3F88038A983CeA5753E11D144228Ebb5eACdE20", 7 | "DisputeModule": "0x9b7A9c70AFF961C799110954fc06F3093aeb94C5", 8 | "EvenSplitGroupPool": "0xf96f2c30b41Cb6e0290de43C8528ae83d4f33F89", 9 | "GroupNFT": "0x4709798FeA84C84ae2475fF0c25344115eE1529f", 10 | "GroupingModule": "0x69D3a7aa9edb72Bc226E745A7cCdd50D947b69Ac", 11 | "IPAccountImplBeacon": "0x9825cc7A398D9C3dDD66232A8Ec76d5b05422581", 12 | "IPAccountImplBeaconProxy": "0x00b800138e4D82D1eea48b414d2a2A8Aee9A33b1", 13 | "IPAccountImplCode": "0x7343646585443F1c3F64E4F08b708788527e1C77", 14 | "IPAssetRegistry": "0x77319B4031e6eF1250907aa00018B8B1c67a244b", 15 | "IPGraphACL": "0x1640A22a8A086747cD377b73954545e2Dfcc9Cad", 16 | "IpRoyaltyVaultBeacon": "0x6928ba25Aa5c410dd855dFE7e95713d83e402AA6", 17 | "IpRoyaltyVaultImpl": "0x63cC7611316880213f3A4Ba9bD72b0EaA2010298", 18 | "LicenseRegistry": "0x529a750E02d8E2f15649c13D69a465286a780e24", 19 | "LicenseToken": "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC", 20 | "LicensingModule": "0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f", 21 | "ModuleRegistry": "0x022DBAAeA5D8fB31a0Ad793335e39Ced5D631fa5", 22 | "PILicenseTemplate": "0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316", 23 | "ProtocolAccessManager": "0xFdece7b8a2f55ceC33b53fd28936B4B1e3153d53", 24 | "ProtocolPauseAdmin": "0xdd661f55128A80437A0c0BDA6E13F214A3B2EB24", 25 | "RoyaltyModule": "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086", 26 | "RoyaltyPolicyLAP": "0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E", 27 | "RoyaltyPolicyLRP": "0x9156e603C949481883B1d3355c6f1132D191fC41" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | libs = ['node_modules', 'lib'] 5 | cache_path = 'forge-cache' 6 | gas_reports = ["*"] 7 | optimizer = true 8 | optimizer_runs = 20000 9 | test = 'test' 10 | solc = '0.8.26' 11 | fs_permissions = [ 12 | { access = 'read-write', path = './deploy-out' }, 13 | { access = 'read', path = './out' }, 14 | ] 15 | ##### Uncomment if running storage layout verification 16 | # ffi = true 17 | # ast = true 18 | # build_info = true 19 | extra_output = ["storageLayout"] 20 | evm_version = "cancun" 21 | 22 | [rpc_endpoints] 23 | story = "${STORY_RPC_URL}" 24 | 25 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 26 | -------------------------------------------------------------------------------- /helper-hardhat-config.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers" 2 | 3 | export interface networkConfigItem { 4 | name?: string 5 | blockConfirmations?: number 6 | } 7 | 8 | export interface networkConfigInfo { 9 | [key: number]: networkConfigItem 10 | } 11 | 12 | export const networkConfig: networkConfigInfo = { 13 | 31337: { 14 | name: "hardhat", 15 | }, 16 | 11155111: { 17 | name: "sepolia", 18 | blockConfirmations: 6, 19 | }, 20 | } 21 | 22 | export const developmentChains = ["hardhat", "localhost"] 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@story-protocol/protocol-core", 3 | "version": "v1.3.2", 4 | "description": "Story Proof-of-Creativity Core smart contracts", 5 | "main": "", 6 | "directories": { 7 | "lib": "lib", 8 | "test": "test" 9 | }, 10 | "author": "PIP Labs", 11 | "license": "UNLICENSED", 12 | "scripts": { 13 | "lint": "npm run lint:js && npm run lint:sol", 14 | "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", 15 | "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint --ignore-path .gitignore .", 16 | "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint --ignore-path .gitignore . --fix", 17 | "lint:sol": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'", 18 | "lint:sol:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --write", 19 | "solhint": "solhint '{contracts,test}/**/*.sol'", 20 | "test": "npx hardhat test", 21 | "prepare": "husky install", 22 | "docgen": "hardhat docgen", 23 | "prepare:test": "ts-node script/hardhat/prepare-test.ts && npx hardhat compile" 24 | }, 25 | "devDependencies": { 26 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.8", 27 | "@nomicfoundation/hardhat-ethers": "^3.0.5", 28 | "@nomicfoundation/hardhat-foundry": "^1.1.1", 29 | "@nomicfoundation/hardhat-verify": "^2.0.3", 30 | "@openzeppelin/hardhat-upgrades": "^3.0.2", 31 | "@tenderly/hardhat-tenderly": "^2.2.1", 32 | "@typechain/ethers-v6": "^0.5.1", 33 | "@typechain/hardhat": "^9.1.0", 34 | "@types/chai": "4", 35 | "@types/mocha": "^10.0.10", 36 | "@types/node": "^22.10.0", 37 | "base64-sol": "^1.1.0", 38 | "chai": "4", 39 | "dotenv": "^16.4.1", 40 | "ds-test": "https://github.com/dapphub/ds-test", 41 | "eslint": "^8.56.0", 42 | "eslint-plugin-prettier": "^5.1.3", 43 | "ethers": "^6", 44 | "forge-std": "github:foundry-rs/forge-std#v1.7.6", 45 | "hardhat": "^2.19.4", 46 | "hardhat-contract-sizer": "^2.10.0", 47 | "hardhat-deploy": "^0.11.45", 48 | "hardhat-deploy-ethers": "^0.4.1", 49 | "hardhat-gas-reporter": "^1.0.10", 50 | "husky": "^8.0.0", 51 | "minimatch": "^9.0.3", 52 | "mocha": "^10.2.0", 53 | "mochawesome": "^7.1.3", 54 | "prettier": "^3.0.0", 55 | "prettier-plugin-solidity": "^1.1.0", 56 | "solhint": "^4.1.1", 57 | "solhint-community": "^3.7.0", 58 | "solhint-plugin-prettier": "^0.1.0", 59 | "solidity-coverage": "^0.8.6", 60 | "solidity-docgen": "^0.6.0-beta.36", 61 | "ts-node": "^10.9.2", 62 | "typechain": "^8.3.2", 63 | "typescript": "^5.7.2" 64 | }, 65 | "dependencies": { 66 | "@openzeppelin/contracts": "5.2.0", 67 | "@openzeppelin/contracts-upgradeable": "5.2.0", 68 | "erc6551": "^0.3.1", 69 | "solady": "^0.0.281" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=node_modules/ds-test/src/ 2 | forge-std/=node_modules/forge-std/src/ 3 | @openzeppelin/=node_modules/@openzeppelin/ 4 | @openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades 5 | @create3-deployer/=lib/create3-deployer 6 | @solady/=node_modules/solady/ -------------------------------------------------------------------------------- /script/foundry/deployment/Main.s.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable no-console */ 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.23; 4 | 5 | import { console2 } from "forge-std/console2.sol"; 6 | 7 | // script 8 | import { DeployHelper } from "../utils/DeployHelper.sol"; 9 | 10 | contract Main is DeployHelper { 11 | address internal ERC6551_REGISTRY = 0x000000006551c19487814612e58FE06813775758; 12 | address internal CREATE3_DEPLOYER = 0x9fBB3DF7C40Da2e5A0dE984fFE2CCB7C47cd0ABf; 13 | uint256 internal CREATE3_DEFAULT_SEED = 6; 14 | address internal IP_GRAPH_ACL = address(0x1640A22a8A086747cD377b73954545e2Dfcc9Cad); 15 | 16 | string internal constant VERSION = "v1.3"; 17 | 18 | constructor() 19 | DeployHelper( 20 | ERC6551_REGISTRY, 21 | address(0), // replaced with WIP in DeployHelper.sol 22 | IP_GRAPH_ACL 23 | ) 24 | {} 25 | 26 | /// @dev To use, run the following command (e.g. for Sepolia): 27 | /// forge script script/foundry/deployment/Main.s.sol:Main --rpc-url $RPC_URL --broadcast --verify -vvvv 28 | 29 | function run() public virtual { 30 | _run(CREATE3_DEPLOYER, CREATE3_DEFAULT_SEED); 31 | } 32 | 33 | function run(uint256 seed) public { 34 | _run(CREATE3_DEPLOYER, seed); 35 | } 36 | 37 | function run(address create3Deployer, uint256 seed) public { 38 | _run(create3Deployer, seed); 39 | } 40 | 41 | function _run(address create3Deployer, uint256 seed) internal { 42 | // deploy all contracts via DeployHelper 43 | super.run( 44 | create3Deployer, 45 | seed, // create3 seed 46 | false, // runStorageLayoutCheck 47 | true, // writeDeployments, 48 | VERSION 49 | ); 50 | _writeDeployment(VERSION); // write deployment json to deployments/deployment-{chainId}.json 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /script/foundry/deployment/MockAssets.s.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable no-console */ 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.23; 4 | 5 | // external 6 | import { console2 } from "forge-std/console2.sol"; 7 | import { Script } from "forge-std/Script.sol"; 8 | import { stdJson } from "forge-std/StdJson.sol"; 9 | // script 10 | import { BroadcastManager } from "../../../script/foundry/utils/BroadcastManager.s.sol"; 11 | import { JsonDeploymentHandler } from "../../../script/foundry/utils/JsonDeploymentHandler.s.sol"; 12 | // test 13 | import { MockERC20 } from "test/foundry/mocks/token/MockERC20.sol"; 14 | import { MockERC721 } from "test/foundry/mocks/token/MockERC721.sol"; 15 | 16 | contract MockAssets is Script, BroadcastManager, JsonDeploymentHandler { 17 | using stdJson for string; 18 | 19 | constructor() JsonDeploymentHandler("main") {} 20 | 21 | /// @dev To use, run the following command (e.g. for Sepolia): 22 | /// forge script script/foundry/deployment/Main.s.sol:Main --rpc-url $RPC_URL --broadcast --verify -vvvv 23 | 24 | function run() public { 25 | _beginBroadcast(); // BroadcastManager.s.sol 26 | 27 | _deployProtocolContracts(); 28 | 29 | _writeDeployment("mock"); // write deployment json to deploy-out/deployment-{chainId}.json 30 | _endBroadcast(); // BroadcastManager.s.sol 31 | } 32 | 33 | function _deployProtocolContracts() private { 34 | _predeploy("MockERC20"); 35 | MockERC20 mockERC20 = new MockERC20(); 36 | _postdeploy("MockERC20", address(mockERC20)); 37 | 38 | _predeploy("MockERC721"); 39 | MockERC721 mockERC721 = new MockERC721("MockERC721"); 40 | _postdeploy("MockERC721", address(mockERC721)); 41 | } 42 | 43 | function _predeploy(string memory contractKey) private view { 44 | console2.log(string.concat("Deploying ", contractKey, "...")); 45 | } 46 | 47 | function _postdeploy(string memory contractKey, address newAddress) private { 48 | console2.log(string.concat(contractKey, " deployed to:"), newAddress); 49 | _writeAddress(contractKey, newAddress); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /script/foundry/upgrades/UpgradeTxGenerator.1.3.2.s.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable no-console */ 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity 0.8.26; 4 | 5 | import { console2 } from "forge-std/console2.sol"; 6 | import { TxGenerator } from "../utils/upgrades/TxGenerator.s.sol"; 7 | 8 | /** 9 | * @title UpgradeTxGenerator 10 | * @dev Script for scheduling, executing, or canceling upgrades for a set of contracts 11 | * 12 | * To use run the script with the following command: 13 | * forge script script/foundry/upgrades/UpgradeTxGenerator.example.s.sol:UpgradeTxGeneratorExample --rpc-url=$RPC_URL --broadcast --priority-gas-price=1 --legacy --private-key=$PRIVATEKEY --skip-simulation 14 | */ 15 | contract UpgradeTxGeneratorExample is TxGenerator { 16 | constructor() TxGenerator( 17 | "v1.3.1", // From version (e.g. v1.2.3) 18 | "v1.3.2" // To version (e.g. v1.3.2) 19 | ) {} 20 | 21 | function _generateActions() internal virtual override { 22 | console2.log("Generating schedule, execute, and cancel txs -------------"); 23 | _generateAction("ModuleRegistry"); 24 | _generateAction("IPAssetRegistry"); 25 | _generateAction("AccessController"); 26 | _generateAction("LicenseRegistry"); 27 | _generateAction("DisputeModule"); 28 | _generateAction("RoyaltyModule"); 29 | _generateAction("GroupNFT"); 30 | _generateAction("GroupingModule"); 31 | _generateAction("LicensingModule"); 32 | _generateAction("LicenseToken"); 33 | _generateAction("RoyaltyPolicyLAP"); 34 | _generateAction("RoyaltyPolicyLRP"); 35 | _generateAction("CoreMetadataModule"); 36 | _generateAction("PILicenseTemplate"); 37 | _generateAction("IpRoyaltyVault"); 38 | _generateAction("EvenSplitGroupPool"); 39 | _generateAction("ArbitrationPolicyUMA"); 40 | _generateAction("ProtocolPauseAdmin"); 41 | _generateAction("IPAccountImplCode"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /script/foundry/upgrades/UpgradeTxGenerator.example.s.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable no-console */ 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity 0.8.26; 4 | 5 | import { console2 } from "forge-std/console2.sol"; 6 | import { TxGenerator } from "../utils/upgrades/TxGenerator.s.sol"; 7 | 8 | /** 9 | * @title UpgradeTxGenerator 10 | * @dev Script for scheduling, executing, or canceling upgrades for a set of contracts 11 | * 12 | * To use run the script with the following command: 13 | * forge script script/foundry/upgrades/UpgradeTxGenerator.example.s.sol:UpgradeTxGeneratorExample --rpc-url=$RPC_URL --broadcast --priority-gas-price=1 --legacy --private-key=$PRIVATEKEY --skip-simulation 14 | */ 15 | contract UpgradeTxGeneratorExample is TxGenerator { 16 | constructor() TxGenerator( 17 | "vx.x.x", // From version (e.g. v1.2.3) 18 | "vx.x.x" // To version (e.g. v1.3.2) 19 | ) {} 20 | 21 | function _generateActions() internal virtual override { 22 | console2.log("Generating schedule, execute, and cancel txs -------------"); 23 | _generateAction("ModuleRegistry"); 24 | _generateAction("IPAssetRegistry"); 25 | _generateAction("AccessController"); 26 | _generateAction("LicenseRegistry"); 27 | _generateAction("DisputeModule"); 28 | _generateAction("RoyaltyModule"); 29 | _generateAction("GroupNFT"); 30 | _generateAction("GroupingModule"); 31 | _generateAction("LicensingModule"); 32 | _generateAction("LicenseToken"); 33 | _generateAction("RoyaltyPolicyLAP"); 34 | _generateAction("RoyaltyPolicyLRP"); 35 | _generateAction("CoreMetadataModule"); 36 | _generateAction("PILicenseTemplate"); 37 | _generateAction("IpRoyaltyVault"); 38 | _generateAction("EvenSplitGroupPool"); 39 | _generateAction("ArbitrationPolicyUMA"); 40 | _generateAction("ProtocolPauseAdmin"); 41 | _generateAction("IPAccountImplCode"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /script/foundry/utils/Create3Deployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.26; 3 | 4 | import { CREATE3 } from "@solady/src/utils/CREATE3.sol"; 5 | import { ICreate3Deployer } from "./ICreate3Deployer.sol"; 6 | 7 | contract Create3Deployer is ICreate3Deployer { 8 | /// @inheritdoc ICreate3Deployer 9 | function deployDeterministic(bytes memory creationCode, bytes32 salt) external payable returns (address deployed) { 10 | return CREATE3.deployDeterministic(creationCode, salt); 11 | } 12 | 13 | /// @inheritdoc ICreate3Deployer 14 | function predictDeterministicAddress(bytes32 salt) external view returns (address deployed) { 15 | return CREATE3.predictDeterministicAddress(salt); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /script/foundry/utils/ICreate3Deployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.26; 3 | 4 | interface ICreate3Deployer { 5 | /// @notice Deploys a contract using CREATE3 6 | /// @param salt The deployer-specific salt for determining the deployed contract's address 7 | /// @param creationCode The creation code of the contract to deploy 8 | /// @return deployed The address of the deployed contract 9 | function deployDeterministic(bytes memory creationCode, bytes32 salt) external payable returns (address deployed); 10 | 11 | /// @notice Predicts the address of a deployed contract 12 | /// @param salt The deployer-specific salt for determining the deployed contract's address 13 | /// @return deployed The address of the contract that will be deployed 14 | function predictDeterministicAddress(bytes32 salt) external view returns (address deployed); 15 | } 16 | -------------------------------------------------------------------------------- /script/foundry/utils/JsonBatchTxHelper.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import { Script } from "forge-std/Script.sol"; 5 | import { stdJson } from "forge-std/StdJson.sol"; 6 | import { console2 } from "forge-std/console2.sol"; 7 | 8 | import { StringUtil } from "./StringUtil.sol"; 9 | 10 | contract JsonBatchTxHelper is Script { 11 | using StringUtil for uint256; 12 | using stdJson for string; 13 | 14 | struct Transaction { 15 | address to; 16 | uint256 value; 17 | bytes data; 18 | uint8 operation; 19 | } 20 | 21 | Transaction[] private transactions; 22 | string private chainId; 23 | 24 | constructor() { 25 | chainId = (block.chainid).toString(); 26 | } 27 | 28 | function _writeTx(address _to, uint256 _value, bytes memory _data) internal { 29 | transactions.push(Transaction({ 30 | to: _to, 31 | value: _value, 32 | data: _data, 33 | operation: 0 34 | })); 35 | console2.log("Added tx to ", _to); 36 | console2.log("Value: ", _value); 37 | console2.log("Data: "); 38 | console2.logBytes(_data); 39 | console2.log("Operation: 0"); 40 | } 41 | 42 | function _writeBatchTxsOutput(string memory _action) internal { 43 | string memory json = "["; 44 | for (uint i = 0; i < transactions.length; i++) { 45 | if (i > 0) { 46 | json = string(abi.encodePacked(json, ",")); 47 | } 48 | json = string(abi.encodePacked(json, "{")); 49 | json = string(abi.encodePacked(json, '"to":"', vm.toString(transactions[i].to), '",')); 50 | json = string(abi.encodePacked(json, '"value":', vm.toString(transactions[i].value), ',')); 51 | json = string(abi.encodePacked(json, '"data":"', vm.toString(transactions[i].data), '",')); 52 | json = string(abi.encodePacked(json, '"operation":', vm.toString(transactions[i].operation))); 53 | json = string(abi.encodePacked(json, "}")); 54 | } 55 | json = string(abi.encodePacked(json, "]")); 56 | 57 | string memory filename = string(abi.encodePacked("./deploy-out/", _action, "-", chainId, ".json")); 58 | vm.writeFile(filename, json); 59 | console2.log("Wrote batch txs to ", filename); 60 | } 61 | } -------------------------------------------------------------------------------- /script/foundry/utils/StringUtil.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | /// @title String Utility Library 5 | library StringUtil { 6 | /// @dev Converts a uint256 into a string. 7 | function toString(uint256 value) internal pure returns (string memory) { 8 | if (value == 0) { 9 | return "0"; 10 | } 11 | uint256 temp = value; 12 | uint256 digits; 13 | while (temp != 0) { 14 | digits++; 15 | temp /= 10; 16 | } 17 | bytes memory buffer = new bytes(digits); 18 | while (value != 0) { 19 | digits -= 1; 20 | buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); 21 | value /= 10; 22 | } 23 | return string(buffer); 24 | } 25 | 26 | function concat(string memory a, string memory b) internal pure returns (string memory) { 27 | return string(abi.encodePacked(a, b)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /script/foundry/utils/upgrades/DeployerUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import { console2 } from "forge-std/console2.sol"; 5 | import { ERC6551Registry } from "erc6551/ERC6551Registry.sol"; 6 | import { ICreate3Deployer } from "../ICreate3Deployer.sol"; 7 | 8 | contract DeployerUtils { 9 | 10 | ERC6551Registry internal immutable erc6551Registry; 11 | ICreate3Deployer internal immutable create3Deployer; 12 | // seed for CREATE3 salt 13 | uint256 internal create3SaltSeed; 14 | 15 | constructor( 16 | address _erc6551Registry, 17 | address _create3Deployer, 18 | uint256 _create3SaltSeed 19 | ) { 20 | erc6551Registry = ERC6551Registry(_erc6551Registry); 21 | create3Deployer = ICreate3Deployer(_create3Deployer); 22 | create3SaltSeed = _create3SaltSeed; 23 | } 24 | 25 | function _getSalt(string memory name) internal virtual view returns (bytes32 salt) { 26 | console2.log(name); 27 | salt = keccak256(abi.encode(name, create3SaltSeed)); 28 | console2.logBytes32(salt); 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /script/foundry/utils/upgrades/StorageLayoutCheck.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import { Script } from "forge-std/Script.sol"; 5 | import { Vm } from "forge-std/Vm.sol"; 6 | import { console } from "forge-std/console.sol"; 7 | import { strings } from "solidity-stringutils/src/strings.sol"; 8 | 9 | import { Utils } from "@openzeppelin-foundry-upgrades/src/internal/Utils.sol"; 10 | import { StringUtil } from "../StringUtil.sol"; 11 | 12 | /// @title StorageLayoutChecker 13 | /// @notice Helper contract to check if the storage layout of the contracts is compatible with upgrades 14 | /// @dev Picked relevant functionality from OpenZeppelin's `@openzeppelin/upgrades-core` package. 15 | /// NOTE: Replace this contract for their library, if these issues are resolved 16 | 17 | /// MUST be called in scripts that deploy or upgrade contracts 18 | /// MUST be called with `--ffi` flag 19 | contract StorageLayoutChecker is Script { 20 | 21 | using strings for *; 22 | 23 | 24 | /// @notice Runs the storage layout check 25 | /// @dev For simplicity and efficiency, we check all the upgradeablecontracts in the project 26 | /// instead of going 1 by 1 using ffi. 27 | function _validate() internal { 28 | string[] memory inputs = _buildValidateCommand(); 29 | Vm.FfiResult memory result = Utils.runAsBashCommand(inputs); 30 | string memory stdout = string(result.stdout); 31 | 32 | // CLI validate command uses exit code to indicate if the validation passed or failed. 33 | // As an extra precaution, we also check stdout for "SUCCESS" to ensure it actually ran. 34 | if (result.exitCode == 0 && stdout.toSlice().contains("SUCCESS".toSlice())) { 35 | return; 36 | } else if (result.stderr.length > 0) { 37 | // Validations failed to run 38 | revert(StringUtil.concat("Failed to run upgrade safety validation: ", string(result.stderr))); 39 | } else { 40 | // Validations ran but some contracts were not upgrade safe 41 | revert(StringUtil.concat("Upgrade safety validation failed:\n", stdout)); 42 | } 43 | } 44 | 45 | function _buildValidateCommand() private view returns (string[] memory) { 46 | string memory outDir = "out"; 47 | 48 | string[] memory inputBuilder = new string[](255); 49 | 50 | uint8 i = 0; 51 | // npx @openzeppelin/upgrades-core validate --requireReference 52 | inputBuilder[i++] = "npx"; 53 | inputBuilder[i++] = string.concat("@openzeppelin/upgrades-core"); 54 | inputBuilder[i++] = "validate"; 55 | inputBuilder[i++] = string.concat(outDir, "/build-info"); 56 | 57 | // Create a copy of inputs but with the correct length 58 | string[] memory inputs = new string[](i); 59 | for (uint8 j = 0; j < i; j++) { 60 | inputs[j] = inputBuilder[j]; 61 | } 62 | 63 | return inputs; 64 | } 65 | } -------------------------------------------------------------------------------- /script/foundry/utils/upgrades/UpgradeImplHelper.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable no-console */ 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.23; 4 | 5 | import { console2 } from "forge-std/console2.sol"; 6 | 7 | contract UpgradedImplHelper { 8 | struct UpgradeProposal { 9 | string key; 10 | address proxy; 11 | address newImpl; 12 | // bytes initCall; TODO 13 | } 14 | 15 | // Upgrade tracking 16 | UpgradeProposal[] public upgradeProposals; 17 | 18 | function _addProposal( 19 | string memory key, 20 | address proxy, 21 | address newImpl 22 | ) internal { 23 | require(proxy != address(0), "UpgradeImplHelper: Invalid proxy address"); 24 | require(newImpl != address(0), "UpgradeImplHelper: Invalid new implementation address"); 25 | upgradeProposals.push( 26 | UpgradeProposal({ 27 | key: key, 28 | proxy: proxy, 29 | newImpl: newImpl 30 | }) 31 | ); 32 | } 33 | 34 | function _logUpgradeProposals() internal view { 35 | console2.log("Upgrade Proposals"); 36 | console2.log("Count", upgradeProposals.length); 37 | for (uint256 i = 0; i < upgradeProposals.length; i++) { 38 | console2.log("Proposal"); 39 | console2.log(upgradeProposals[i].key); 40 | if (keccak256(abi.encodePacked(upgradeProposals[i].key)) == keccak256(abi.encodePacked("IpRoyaltyVault"))) { 41 | console2.log("BeaconProxy"); 42 | } else { 43 | console2.log("Proxy"); 44 | } 45 | console2.log(upgradeProposals[i].proxy); 46 | console2.log("New Impl"); 47 | console2.log(upgradeProposals[i].newImpl); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /script/foundry/utils/upgrades/UpgradedImplHelper.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable no-console */ 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.23; 4 | 5 | import { console2 } from "forge-std/console2.sol"; 6 | 7 | contract UpgradedImplHelper { 8 | struct UpgradeProposal { 9 | string key; 10 | address proxy; 11 | address newImpl; 12 | // bytes initCall; TODO 13 | } 14 | 15 | // Upgrade tracking 16 | UpgradeProposal[] public upgradeProposals; 17 | 18 | function _addProposal( 19 | string memory key, 20 | address proxy, 21 | address newImpl 22 | ) internal { 23 | require(proxy != address(0), "UpgradeImplHelper: Invalid proxy address"); 24 | require(newImpl != address(0), "UpgradeImplHelper: Invalid new implementation address"); 25 | upgradeProposals.push( 26 | UpgradeProposal({ 27 | key: key, 28 | proxy: proxy, 29 | newImpl: newImpl 30 | }) 31 | ); 32 | } 33 | 34 | function _logUpgradeProposals() internal view { 35 | console2.log("Upgrade Proposals"); 36 | console2.log("Count", upgradeProposals.length); 37 | for (uint256 i = 0; i < upgradeProposals.length; i++) { 38 | console2.log("Proposal"); 39 | console2.log(upgradeProposals[i].key); 40 | if (keccak256(abi.encodePacked(upgradeProposals[i].key)) == keccak256(abi.encodePacked("IpRoyaltyVault"))) { 41 | console2.log("BeaconProxy"); 42 | } else { 43 | console2.log("Proxy"); 44 | } 45 | console2.log(upgradeProposals[i].proxy); 46 | console2.log("New Impl"); 47 | console2.log(upgradeProposals[i].newImpl); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /script/hardhat/deployment/01-deploy-mock.ts: -------------------------------------------------------------------------------- 1 | import hre from "hardhat" 2 | 3 | import { MockAsset } from "../utils/mock-assets" 4 | 5 | async function runMock() { 6 | const { deployments, getNamedAccounts, ethers } = hre 7 | const { deploy: deployFn } = deployments 8 | const { deployer } = await getNamedAccounts() 9 | const deployerSigner = await ethers.getSigner(deployer) 10 | 11 | await MockAsset.deploy(deployFn, deployer, deployerSigner) 12 | } 13 | 14 | runMock().catch((error) => { 15 | console.error(error) 16 | process.exitCode = 1 17 | }) 18 | -------------------------------------------------------------------------------- /script/hardhat/pre-deployment/00-fund-accounts.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from "ethers" 2 | import hre from "hardhat" 3 | 4 | require("dotenv").config() 5 | 6 | import { USERS } from "../utils/constants" 7 | 8 | async function runFundAccounts() { 9 | const provider = new ethers.JsonRpcProvider((hre.network.config as { url: string }).url) 10 | 11 | if (!process.env.MAINNET_DEPLOYER_ADDRESS) throw new Error("MAINNET_DEPLOYER_ADDRESS not set") 12 | const wallets: string[] = [ 13 | process.env.MAINNET_DEPLOYER_ADDRESS as string, 14 | USERS.ALICE, 15 | USERS.BOB, 16 | USERS.CARL, 17 | USERS.EVE, 18 | ] 19 | 20 | await provider.send("tenderly_setBalance", [ 21 | wallets, 22 | // amount in wei will be set for ALL wallets 23 | ethers.toQuantity(ethers.parseUnits("10000", "ether")), 24 | ]) 25 | 26 | for (const wallet of wallets) { 27 | const balance = await provider.getBalance(wallet) 28 | console.log(`Balance of ${wallet} is ${ethers.formatEther(balance)}`) 29 | } 30 | } 31 | 32 | runFundAccounts().catch((error) => { 33 | console.error(error) 34 | process.exitCode = 1 35 | }) 36 | -------------------------------------------------------------------------------- /script/hardhat/prepare-test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | async function main() { 5 | // copy mock contracts to contracts directory 6 | // this is a workaround to avoid the issue that the mock contracts are not found by hardhat 7 | // because the mock contracts are in the foundry test directory 8 | const mockFilesPath = [ 9 | 'test/foundry/mocks/module/MockLicenseTemplate.sol', 10 | ]; 11 | 12 | for (const filePath of mockFilesPath) { 13 | const fileName = filePath.split('/').pop() || ''; 14 | const sourceFile = path.join(__dirname, '..', '..', filePath); 15 | const targetDir = path.join(__dirname, '..', '..', 'contracts'); 16 | const targetFile = path.join(targetDir, fileName); 17 | 18 | // ensure target directory exists 19 | if (!fs.existsSync(targetDir)) { 20 | fs.mkdirSync(targetDir, { recursive: true }); 21 | } 22 | 23 | // check if source file exists 24 | if (!fs.existsSync(sourceFile)) { 25 | throw new Error(`Source file not found: ${sourceFile}`); 26 | } 27 | 28 | // copy file 29 | fs.copyFileSync(sourceFile, targetFile); 30 | console.log(`Mock contract has been copied from ${sourceFile} to ${targetFile}`); 31 | } 32 | 33 | } 34 | 35 | main() 36 | .then(() => process.exit(0)) 37 | .catch((error) => { 38 | console.error(error); 39 | process.exit(1); 40 | }); 41 | -------------------------------------------------------------------------------- /script/hardhat/utils/constants.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// !!!! WARNING !!!! 3 | /// DO NOT USE THESE ADDRESSES IN PRODUCTION 4 | /// THESE ADDRESSES ARE FOR TESTING ONLY 5 | /// THE PRIVATE KEYS ARE FROM COMMON ANVIL MNEMONIC 6 | /// 'test test test test test test test test test test test junk' 7 | /// 8 | 9 | export const USERS = { 10 | ALICE: "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", 11 | ALICE_PRIVATE_KEY: "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", 12 | BOB: "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", 13 | BOB_PRIVATE_KEY: "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", 14 | CARL: "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", 15 | CARL_PRIVATE_KEY: "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", 16 | EVE: "0x976EA74026E726554dB657fA54763abd0C3a0aa9", 17 | EVE_PRIVATE_KEY: "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", 18 | } 19 | -------------------------------------------------------------------------------- /script/hardhat/utils/deployed.ts: -------------------------------------------------------------------------------- 1 | import * as deployedAll from "../../out/all.json" 2 | import { 3 | DisputeModule, 4 | DisputeModule__factory, 5 | IERC6551Registry, 6 | IERC6551Registry__factory, 7 | IPAccountRegistry, 8 | IPAccountRegistry__factory, 9 | IPMetadataResolver, 10 | IPMetadataResolver__factory, 11 | IPRecordRegistry, 12 | IPRecordRegistry__factory, 13 | LicenseRegistry, 14 | LicenseRegistry__factory, 15 | ModuleRegistry, 16 | ModuleRegistry__factory, 17 | RegistrationModule, 18 | RegistrationModule__factory, 19 | RoyaltyModule, 20 | RoyaltyModule__factory, 21 | TaggingModule, 22 | TaggingModule__factory, 23 | } from "../../../typechain" 24 | 25 | export interface DeployedContracts { 26 | // Registries 27 | ERC6551Registry: IERC6551Registry 28 | IPAccountRegistry: IPAccountRegistry 29 | IPRecordRegistry: IPRecordRegistry 30 | LicenseRegistry: LicenseRegistry 31 | ModuleRegistry: ModuleRegistry 32 | // Resolvers 33 | IPMetadataResolver: IPMetadataResolver 34 | // Modules 35 | DisputeModule: DisputeModule 36 | RoyaltyModule: RoyaltyModule 37 | TaggingModule: TaggingModule 38 | RegistrationModule: RegistrationModule 39 | } 40 | 41 | export function getDeployedContracts(deployerSigner: any) { 42 | const ERC6551_REGISTRY = "0x000000006551c19487814612e58FE06813775758" // v0.3.1 Ethereum 43 | 44 | return { 45 | // Registries 46 | ERC6551Registry: IERC6551Registry__factory.connect(ERC6551_REGISTRY, deployerSigner), 47 | IPAccountRegistry: IPAccountRegistry__factory.connect(deployedAll.contracts.IPAccountRegistry, deployerSigner), 48 | IPRecordRegistry: IPRecordRegistry__factory.connect(deployedAll.contracts.IPRecordRegistry, deployerSigner), 49 | LicenseRegistry: LicenseRegistry__factory.connect(deployedAll.contracts.LicenseRegistry, deployerSigner), 50 | ModuleRegistry: ModuleRegistry__factory.connect(deployedAll.contracts.ModuleRegistry, deployerSigner), 51 | // Resolvers 52 | IPMetadataResolver: IPMetadataResolver__factory.connect(deployedAll.contracts.IPMetadataResolver, deployerSigner), 53 | // Modules 54 | DisputeModule: DisputeModule__factory.connect(deployedAll.contracts.DisputeModule, deployerSigner), 55 | RoyaltyModule: RoyaltyModule__factory.connect(deployedAll.contracts.RoyaltyModule, deployerSigner), 56 | TaggingModule: TaggingModule__factory.connect(deployedAll.contracts.TaggingModule, deployerSigner), 57 | RegistrationModule: RegistrationModule__factory.connect(deployedAll.contracts.RegistrationModule, deployerSigner), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /script/hardhat/utils/interfaces.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/dethcrypto/TypeChain/blob/master/packages/target-ethers-v5/static/common.ts 2 | import type { EventFilter } from "ethers" 3 | 4 | export interface TypedEvent = any, TArgsObject = any> extends Event { 5 | args: TArgsArray & TArgsObject 6 | } 7 | 8 | export interface TypedEventFilter<_TEvent extends TypedEvent> extends EventFilter {} 9 | -------------------------------------------------------------------------------- /script/hardhat/utils/mock-assets.ts: -------------------------------------------------------------------------------- 1 | import { Contract, ContractTransactionReceipt } from "ethers" 2 | import { DeployResult } from "hardhat-deploy/dist/types" 3 | import * as fs from "fs" 4 | import * as path from "path" 5 | 6 | import * as mockTokens from "../../out/mock/tokens.json" 7 | import { MockERC20, MockERC20__factory, MockERC721, MockERC721__factory } from "../../../typechain" 8 | 9 | const erc721MintAbi = [ 10 | { 11 | inputs: [ 12 | { 13 | internalType: "address", 14 | name: "to", 15 | type: "address", 16 | }, 17 | ], 18 | name: "mint", 19 | outputs: [ 20 | { 21 | internalType: "uint256", 22 | name: "id", 23 | type: "uint256", 24 | }, 25 | ], 26 | payable: false, 27 | stateMutability: "nonpayable", 28 | type: "function", 29 | }, 30 | ] 31 | 32 | export class MockAsset { 33 | public MockToken: MockERC20 34 | public MockNFT: MockERC721 35 | public addr: { MockToken: string; MockNFT: string } 36 | 37 | private deployerSigner: any 38 | 39 | constructor(deployerSigner: any) { 40 | this.deployerSigner = deployerSigner 41 | this.MockToken = MockERC20__factory.connect(mockTokens.MockToken, deployerSigner) 42 | this.MockNFT = MockERC721__factory.connect(mockTokens.MockNFT, deployerSigner) 43 | 44 | this.addr = { 45 | MockToken: mockTokens.MockToken, 46 | MockNFT: mockTokens.MockNFT, 47 | } 48 | } 49 | 50 | static async deploy(deployFn: any, deployer: string, deployerSigner: any) { 51 | let deployRes: DeployResult 52 | 53 | deployRes = await deployFn("MockERC20", { 54 | from: deployer, 55 | log: true, 56 | waitConfirmations: 1, 57 | }) 58 | const MockToken = MockERC20__factory.connect(deployRes.address, deployerSigner) 59 | await MockToken.waitForDeployment() 60 | 61 | deployRes = await deployFn("MockERC721", { 62 | from: deployer, 63 | log: true, 64 | waitConfirmations: 1, 65 | }) 66 | const MockNFT = MockERC721__factory.connect(deployRes.address, deployerSigner) 67 | await MockNFT.waitForDeployment() 68 | 69 | // save MockToken and MockNFT addresses to json in this folder 70 | const outMockPath = path.join(__dirname, "../../out/mock") 71 | const mockPath = path.join(outMockPath, "tokens.json") 72 | const mock = { MockToken: await MockToken.getAddress(), MockNFT: await MockNFT.getAddress() } 73 | fs.writeFileSync(mockPath, JSON.stringify(mock, null, 2)) 74 | 75 | return new MockAsset(deployerSigner) 76 | } 77 | 78 | async mint20(recipient: string, amount: number): Promise { 79 | const decimals = BigInt(await this.MockToken.decimals()) 80 | const txRes = await this.MockToken.mint(recipient, BigInt(amount) * decimals) 81 | await txRes.wait() 82 | return this.MockToken.balanceOf(recipient) 83 | } 84 | 85 | async mint721(recipient: string): Promise { 86 | const tokenAddr = await this.MockNFT.getAddress() 87 | const txRes = await this.MockNFT.mint(recipient) 88 | const receipt = (await txRes.wait()) as ContractTransactionReceipt 89 | 90 | // get token ID from the receipt 91 | const newTokenId = receipt.logs?.[0].topics?.[3] 92 | if (!newTokenId) throw new Error("tokenId not found in receipt") 93 | 94 | return BigInt(newTokenId) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /script/hardhat/utils/verify.ts: -------------------------------------------------------------------------------- 1 | import { run } from "hardhat" 2 | 3 | export const verify = async (contractAddress: string, args: any[]) => { 4 | console.log("Verifying contract...") 5 | 6 | try { 7 | await run("verify:verify", { 8 | address: contractAddress, 9 | constructorArguments: args, 10 | }) 11 | console.log("Contract verified!") 12 | } catch (error: any) { 13 | if (error.message.toLowerCase().includes("already verified")) { 14 | console.log("Already Verified!") 15 | } else { 16 | console.log("Verification failed!") 17 | console.log(error) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "lib", 3 | "solc_remaps": [ 4 | "ds-test/=node_modules/ds-test/src/", 5 | "forge-std/=node_modules/forge-std/src/", 6 | "@openzeppelin/=node_modules/@openzeppelin/", 7 | "@openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades" 8 | ] 9 | } -------------------------------------------------------------------------------- /tenderly.yaml: -------------------------------------------------------------------------------- 1 | account_id: 179b2237-715d-4ea3-9c29-dfa0145fd536 2 | project_slug: story/beta-test 3 | provider: Hardhat 4 | -------------------------------------------------------------------------------- /test/foundry/IPAccountImpl.tree: -------------------------------------------------------------------------------- 1 | IPAccountImpl.sol 2 | # when checking supported interfaces 3 | ## it should support IIPAccount interface 4 | ## it should support IIPAccountStorage interface 5 | ## it should support IERC6551Account interface 6 | ## it should support IERC1155Receiver interface 7 | ## it should support IERC721Receiver interface 8 | ## it should support IERC165 interface 9 | # when checking identifier of IP 10 | ## it should return expected chain ID 11 | ## it should return expected token contract 12 | ## it should return expected token ID 13 | # when checking if signer is valid 14 | ## when the data length is greater than zero and less than four 15 | ### it should revert 16 | ## when the data length is zero or greater than or equal to four 17 | ### when checking permission via access controller fails 18 | #### it should revert 19 | ### when checking permission via access controller succeeds 20 | #### it should true 21 | #### it sohuld return IERC6551Account.isValidSigner.selector 22 | # when executing 23 | ## when signer is invalid in access controlelr 24 | ### it should revert 25 | ## when signer is valid in access controller 26 | ### when call fails 27 | #### it should revert 28 | ### when call succeeds 29 | #### it should increment `state` 30 | #### it should emit an event 31 | #### it should return result 32 | # when executing with signature 33 | ## given the signer is zero address 34 | ### it should revert 35 | ## given the deadline is in the past 36 | ### it should revert 37 | ## given the EIP1976 signature is invalid now 38 | ### it should revert 39 | ## given the EIP1976 signature is valid 40 | ### it should call `_execute` 41 | ### it should emit an event 42 | # when receiving ERC721 43 | ## it should return onERC721Received selector 44 | # when receiving ERC1155 45 | ## given batch received 46 | ### it should return onERC1155BatchReceived selector 47 | ## given single received 48 | ### it should return onERC1155Received selector -------------------------------------------------------------------------------- /test/foundry/access/IPGraphACL.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { BaseTest } from "../utils/BaseTest.t.sol"; 5 | import { IIPGraphACL } from "../../../contracts/interfaces/access/IIPGraphACL.sol"; 6 | 7 | contract IPGraphACLTest is BaseTest { 8 | function setUp() public override { 9 | super.setUp(); 10 | } 11 | 12 | // test allow/disallow 13 | // test add/remove whitelist 14 | // onlyWhitelisted modifier 15 | 16 | function test_IPGraphACL_addToWhitelist() public { 17 | vm.prank(admin); 18 | vm.expectEmit(); 19 | emit IIPGraphACL.WhitelistedAddress(address(0x123)); 20 | ipGraphACL.whitelistAddress(address(0x123)); 21 | vm.prank(address(0x123)); 22 | assertTrue(ipGraphACL.isWhitelisted(address(0x123))); 23 | } 24 | 25 | function test_IPGraphACL_revert_removeFromWhitelist() public { 26 | vm.prank(admin); 27 | ipGraphACL.whitelistAddress(address(0x123)); 28 | vm.prank(address(0x123)); 29 | assertTrue(ipGraphACL.isWhitelisted(address(0x123))); 30 | vm.prank(admin); 31 | vm.expectEmit(); 32 | emit IIPGraphACL.RevokedWhitelistedAddress(address(0x123)); 33 | ipGraphACL.revokeWhitelistedAddress(address(0x123)); 34 | assertFalse(ipGraphACL.isWhitelisted(address(0x123))); 35 | vm.prank(address(0x123)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/foundry/access/btt/AccessControlled.tree: -------------------------------------------------------------------------------- 1 | AccessControlled.sol 2 | ├── when verifying permission for IP 3 | │ ├── when `ipAccount` is not a valid SP IP Account address 4 | │ │ └── it should revert 5 | │ └── when `ipAccount` is a valid SP IP Account 6 | │ ├── when `ipAccount` is caller 7 | │ │ └── it should return 8 | │ └── when `ipAccount` is not caller 9 | │ └── it should check permission via Access Controller 10 | └── when checking if IP has permission 11 | ├── when `ipAccount` is not a valid SP IP Account address 12 | │ └── it should return false 13 | └── when `ipAccount` is a valid SP IP Account 14 | ├── when `ipAccount` is caller 15 | │ └── it should return true 16 | └── when `ipAccount` is not caller 17 | ├── when Access Controller check permission returns 18 | │ └── it should return true 19 | └── when Access Controller check permission reverts 20 | └── it should return false -------------------------------------------------------------------------------- /test/foundry/access/btt/AccessController.checkPermission.tree: -------------------------------------------------------------------------------- 1 | AccessController.sol:checkPermission 2 | ├── given the `ipAccount` is not a valid SP IP Account address 3 | │ └── it should revert 4 | ├── given the `signer` is IP owner 5 | │ └── it should return 6 | ├── given the `to` is not Access Controller and neither `to` or `signer` are registered module 7 | │ └── it should revert 8 | └── given the validations pass 9 | ├── given the perm(ipAccount, signer, to, func) is ALLOW 10 | │ └── it should return 11 | ├── given the perm(ipAccount, signer, to, func) is DENY 12 | │ └── it should revert 13 | └── given the perm(ipAccount, signer, to, func) is ABSTAIN 14 | ├── given the perm(ipAccount, signer, to, wildcard) is ALLOW 15 | │ └── it should return 16 | ├── given the perm(ipAccount, signer, to, wildcard) is DENY 17 | │ └── it should revert 18 | └── given the perm(ipAccount, signer, to, wildcard) is ABSTAIN 19 | ├── given the perm(ipAccount, signer, wildcard, wildcard) is ALLOW 20 | │ └── it should return 21 | └── given the perm(ipAccount, signer, wildcard, wildcard) is DENY 22 | └── it should revert -------------------------------------------------------------------------------- /test/foundry/access/btt/AccessController.setPermission.tree: -------------------------------------------------------------------------------- 1 | AccessController.sol:setPermission 2 | ├── given the `ipAccount` is zero address 3 | │ └── it should revert 4 | ├── given the `signer` is zero address 5 | │ └── it should revert 6 | ├── given the `ipAccount` is not a valid SP IP Account address 7 | │ └── it should revert 8 | ├── given the `permission` is not ABSTAIN, ALLOW, and DENY 9 | │ └── it should revert 10 | ├── given the caller is not `ipAccount` or its owner 11 | │ └── it should revert 12 | └── given the `permission` is ABSTAIN, ALLOW, or DENY 13 | ├── it should set (`ipAccount` owner => `ipAccount` => `signer` => `to` => `func`) = `permission` 14 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/integration/big-bang/README.md: -------------------------------------------------------------------------------- 1 | ## Integration: Big Bang 2 | 3 | Integration tests involving all components and core modules. NO mocks, all actual contracts. -------------------------------------------------------------------------------- /test/foundry/integration/flows/disputes/README.md: -------------------------------------------------------------------------------- 1 | # DISPUTED CASES 2 | 3 | # Plagiarism 4 | Bob owns IP1 5 | Bob sets P1 in IP1 (he can, since he claims IP1 is original) 6 | Alice owns IP2 7 | Alice mints L1 from IP1-P1 8 | Alice links IP2 to IP1 with L1 9 | Dan finds out IP1 plagiarizes his work, IP0 10 | Dan raises dispute against IP1 for plagiarism 11 | Dispute passes, IP1 is labeled as plagiarism 12 | Bob cannot set policies in IP1 13 | Bob cannot mint licenses from IP1 14 | Alice cannot set policies in IP2 15 | Alice cannot mint licenses from IP2 16 | -------------------------------------------------------------------------------- /test/foundry/invariants/BaseInvariant.t.sol: -------------------------------------------------------------------------------- 1 | /* solhint-disable no-console */ 2 | // SPDX-License-Identifier: BUSL-1.1 3 | pragma solidity 0.8.26; 4 | 5 | // external 6 | import { BaseTest } from "../utils/BaseTest.t.sol"; 7 | 8 | contract BaseInvariant is BaseTest { 9 | function setUp() public override { 10 | super.setUp(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/foundry/mocks/CustomModuleType.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; 5 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 6 | import { BaseModule } from "../../../contracts/modules/BaseModule.sol"; 7 | 8 | interface ICustomModule is IModule { 9 | function customFunction() external; 10 | } 11 | 12 | contract CustomModule is ICustomModule, BaseModule { 13 | string public constant override name = "CustomModule"; 14 | 15 | function customFunction() external override { 16 | // do nothing 17 | } 18 | 19 | function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { 20 | return interfaceId == type(ICustomModule).interfaceId || super.supportsInterface(interfaceId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/foundry/mocks/MockProtocolPausable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { ProtocolPausableUpgradeable } from "contracts/pause/ProtocolPausableUpgradeable.sol"; 5 | 6 | contract MockProtocolPausable is ProtocolPausableUpgradeable { 7 | function initialize(address accessManager) public initializer { 8 | __ProtocolPausable_init(accessManager); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/foundry/mocks/MockTokenGatedHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 6 | import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 7 | import { IHookModule } from "../../../contracts/interfaces/modules/base/IHookModule.sol"; 8 | import { BaseModule } from "../../../contracts/modules/BaseModule.sol"; 9 | 10 | /// @title Mock Token Gated Hook. 11 | /// @notice Hook for ensursing caller is the owner of an NFT token. 12 | contract MockTokenGatedHook is BaseModule, IHookModule { 13 | using ERC165Checker for address; 14 | 15 | string public constant override name = "MockTokenGatedHook"; 16 | 17 | function verify(address ipId, address caller, bytes calldata data) external view returns (bool) { 18 | address tokenAddress = abi.decode(data, (address)); 19 | if (caller == address(0)) { 20 | return false; 21 | } 22 | if (!tokenAddress.supportsInterface(type(IERC721).interfaceId)) { 23 | return false; 24 | } 25 | return IERC721(tokenAddress).balanceOf(caller) > 0; 26 | } 27 | 28 | function validateConfig(bytes calldata configData) external view override { 29 | address tokenAddress = abi.decode(configData, (address)); 30 | require(tokenAddress.supportsInterface(type(IERC721).interfaceId), "MockTokenGatedHook: Invalid token address"); 31 | } 32 | 33 | function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { 34 | return interfaceId == type(IHookModule).interfaceId || super.supportsInterface(interfaceId); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/foundry/mocks/dispute/MockArbitrationPolicy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | import { IDisputeModule } from "../../../../contracts/interfaces/modules/dispute/IDisputeModule.sol"; 8 | import { IArbitrationPolicy } from "../../../../contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol"; 9 | 10 | contract MockArbitrationPolicy is IArbitrationPolicy { 11 | using SafeERC20 for IERC20; 12 | 13 | address public immutable DISPUTE_MODULE; 14 | address public immutable PAYMENT_TOKEN; 15 | uint256 public immutable ARBITRATION_PRICE; 16 | 17 | address treasury; 18 | 19 | error MockArbitrationPolicy__NotDisputeModule(); 20 | 21 | /// @notice Restricts the calls to the DisputeModule 22 | modifier onlyDisputeModule() { 23 | if (msg.sender != DISPUTE_MODULE) revert MockArbitrationPolicy__NotDisputeModule(); 24 | _; 25 | } 26 | 27 | constructor(address disputeModule, address paymentToken, uint256 arbitrationPrice) { 28 | DISPUTE_MODULE = disputeModule; 29 | PAYMENT_TOKEN = paymentToken; 30 | ARBITRATION_PRICE = arbitrationPrice; 31 | } 32 | 33 | function setTreasury(address newTreasury) external { 34 | treasury = newTreasury; 35 | } 36 | 37 | function onRaiseDispute( 38 | address caller, 39 | address targetIpId, 40 | address disputeInitiator, 41 | bytes32 disputeEvidenceHash, 42 | bytes32 targetTag, 43 | uint256 disputeId, 44 | bytes calldata data 45 | ) external onlyDisputeModule { 46 | IERC20(PAYMENT_TOKEN).safeTransferFrom(caller, address(this), ARBITRATION_PRICE); 47 | } 48 | 49 | function onDisputeJudgement(uint256 disputeId, bool decision, bytes calldata data) external onlyDisputeModule { 50 | if (decision) { 51 | (, address disputeInitiator, , , , , , ) = IDisputeModule(DISPUTE_MODULE).disputes(disputeId); 52 | IERC20(PAYMENT_TOKEN).safeTransfer(disputeInitiator, ARBITRATION_PRICE); 53 | } else { 54 | IERC20(PAYMENT_TOKEN).safeTransfer(treasury, ARBITRATION_PRICE); 55 | } 56 | } 57 | 58 | function onDisputeCancel(address caller, uint256 disputeId, bytes calldata data) external onlyDisputeModule {} 59 | 60 | function onResolveDispute(address caller, uint256 disputeId, bytes calldata data) external onlyDisputeModule {} 61 | } 62 | -------------------------------------------------------------------------------- /test/foundry/mocks/dispute/MockIpAssetRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | contract MockIpAssetRegistry { 5 | function isRegistered(address ipId) external view returns (bool) { 6 | return true; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/foundry/mocks/module/LicenseRegistryHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { LicenseRegistry } from "../../../../contracts/registries/LicenseRegistry.sol"; 5 | 6 | contract LicenseRegistryHarness is LicenseRegistry { 7 | constructor( 8 | address _groupIpAssetRegistry, 9 | address _erc721Registry, 10 | address _erc1155Registry, 11 | address _ipGraphAcl 12 | ) LicenseRegistry(_groupIpAssetRegistry, _erc721Registry, _erc1155Registry, _ipGraphAcl) {} 13 | 14 | function setExpirationTime(address ipId, uint256 expireTime) external { 15 | _setExpirationTime(ipId, expireTime); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/foundry/mocks/module/MockAccessControllerV2.sol: -------------------------------------------------------------------------------- 1 | import { AccessController } from "contracts/access/AccessController.sol"; 2 | 3 | // SPDX-License-Identifier: BUSL-1.1 4 | pragma solidity 0.8.26; 5 | 6 | contract MockAccessControllerV2 is AccessController { 7 | /// @dev Storage structure for the AccessControllerV2 8 | /// @custom:storage-location erc7201:story-protocol.AccessControllerV2 9 | struct AccessControllerV2Storage { 10 | string newState; 11 | } 12 | 13 | // keccak256(abi.encode(uint256(keccak256("story-protocol.AccessControllerV2")) - 1)) & ~bytes32(uint256(0xff)); 14 | bytes32 private constant AccessControllerV2StorageLocation = 15 | 0xf328f2cdee4ae4df23921504bfa43e3156fb4d18b23549ca0a43fd1e64947a00; 16 | 17 | /// @custom:oz-upgrades-unsafe-allow constructor 18 | constructor( 19 | address ipAccountRegistry, 20 | address moduleRegistry 21 | ) AccessController(ipAccountRegistry, moduleRegistry) {} 22 | 23 | function initialize() public reinitializer(2) { 24 | _getAccessControllerV2Storage().newState = "initialized"; 25 | } 26 | 27 | function get() external view returns (string memory) { 28 | return _getAccessControllerV2Storage().newState; 29 | } 30 | 31 | /// @dev Returns the storage struct of AccessControllerV2. 32 | function _getAccessControllerV2Storage() private pure returns (AccessControllerV2Storage storage $) { 33 | assembly { 34 | $.slot := AccessControllerV2StorageLocation 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/foundry/mocks/module/MockIpRoyaltyVaultV2.sol: -------------------------------------------------------------------------------- 1 | import { IpRoyaltyVault } from "contracts/modules/royalty/policies/IpRoyaltyVault.sol"; 2 | 3 | // SPDX-License-Identifier: BUSL-1.1 4 | pragma solidity 0.8.26; 5 | 6 | contract MockIpRoyaltyVaultV2 is IpRoyaltyVault { 7 | /// @dev Storage structure for the MockIPRoyaltyVaultV2 8 | /// @custom:storage-location erc7201:story-protocol.MockIPRoyaltyVaultV2 9 | struct MockIPRoyaltyVaultV2Storage { 10 | string newState; 11 | } 12 | 13 | // keccak256(abi.encode(uint256(keccak256("story-protocol.MockIPRoyaltyVaultV2")) - 1)) & ~bytes32(uint256(0xff)); 14 | bytes32 private constant MockIPRoyaltyVaultV2StorageLocation = 15 | 0x2942176f94974e015a9b06f79a3a2280d18f1872591c134ba237fa184e378300; 16 | 17 | /// @custom:oz-upgrades-unsafe-allow constructor 18 | constructor( 19 | address royaltyPolicyLAP, 20 | address disputeModule, 21 | address licenseRegistry, 22 | address groupingModule 23 | ) IpRoyaltyVault(royaltyPolicyLAP, disputeModule, licenseRegistry, groupingModule) { 24 | _disableInitializers(); 25 | } 26 | 27 | function set(string calldata value) external { 28 | _getMockIPRoyaltyVaultV2Storage().newState = value; 29 | } 30 | 31 | function get() external view returns (string memory) { 32 | return _getMockIPRoyaltyVaultV2Storage().newState; 33 | } 34 | 35 | /// @dev Returns the storage struct of MockIPRoyaltyVaultV2. 36 | function _getMockIPRoyaltyVaultV2Storage() private pure returns (MockIPRoyaltyVaultV2Storage storage $) { 37 | assembly { 38 | $.slot := MockIPRoyaltyVaultV2StorageLocation 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/foundry/mocks/module/MockLicensingHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | 6 | import { BaseModule } from "../../../../contracts/modules/BaseModule.sol"; 7 | import { ILicensingHook } from "contracts/interfaces/modules/licensing/ILicensingHook.sol"; 8 | 9 | contract MockLicensingHook is BaseModule, ILicensingHook { 10 | string public constant override name = "MockLicensingHook"; 11 | 12 | function beforeMintLicenseTokens( 13 | address caller, 14 | address licensorIpId, 15 | address licenseTemplate, 16 | uint256 licenseTermsId, 17 | uint256 amount, 18 | address receiver, 19 | bytes calldata hookData 20 | ) external returns (uint256 totalMintingFee) { 21 | address unqualifiedAddress = abi.decode(hookData, (address)); 22 | if (caller == unqualifiedAddress) revert("MockLicensingHook: caller is invalid"); 23 | if (receiver == unqualifiedAddress) revert("MockLicensingHook: receiver is invalid"); 24 | return amount * 100; 25 | } 26 | 27 | function beforeRegisterDerivative( 28 | address caller, 29 | address childIpId, 30 | address parentIpId, 31 | address licenseTemplate, 32 | uint256 licenseTermsId, 33 | bytes calldata hookData 34 | ) external returns (uint256 mintingFee) { 35 | address unqualifiedAddress = abi.decode(hookData, (address)); 36 | if (caller == unqualifiedAddress) revert("MockLicensingHook: caller is invalid"); 37 | return 100; 38 | } 39 | 40 | function calculateMintingFee( 41 | address caller, 42 | address licensorIpId, 43 | address licenseTemplate, 44 | uint256 licenseTermsId, 45 | uint256 amount, 46 | address receiver, 47 | bytes calldata hookData 48 | ) external view returns (uint256 totalMintingFee) { 49 | address unqualifiedAddress = abi.decode(hookData, (address)); 50 | if (caller == unqualifiedAddress) revert("MockLicensingHook: caller is invalid"); 51 | if (receiver == unqualifiedAddress) revert("MockLicensingHook: receiver is invalid"); 52 | return amount * 100; 53 | } 54 | 55 | function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { 56 | return interfaceId == type(ILicensingHook).interfaceId || super.supportsInterface(interfaceId); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/foundry/mocks/module/MockModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 5 | 6 | import { IIPAccount } from "../../../../contracts/interfaces/IIPAccount.sol"; 7 | import { IModule } from "../../../../contracts/interfaces/modules/base/IModule.sol"; 8 | import { IIPAssetRegistry } from "../../../../contracts/interfaces/registries/IIPAssetRegistry.sol"; 9 | import { IModuleRegistry } from "../../../../contracts/interfaces/registries/IModuleRegistry.sol"; 10 | import { IPAccountChecker } from "../../../../contracts/lib/registries/IPAccountChecker.sol"; 11 | import { BaseModule } from "../../../../contracts/modules/BaseModule.sol"; 12 | 13 | contract MockModule is BaseModule { 14 | using ERC165Checker for address; 15 | using IPAccountChecker for IIPAssetRegistry; 16 | 17 | IIPAssetRegistry public ipAccountRegistry; 18 | IModuleRegistry public moduleRegistry; 19 | string public name; 20 | 21 | constructor(address _ipAccountRegistry, address _moduleRegistry, string memory _name) { 22 | ipAccountRegistry = IIPAssetRegistry(_ipAccountRegistry); 23 | moduleRegistry = IModuleRegistry(_moduleRegistry); 24 | name = _name; 25 | } 26 | 27 | function executeSuccessfully(string memory param) external view returns (string memory) { 28 | require(ipAccountRegistry.isIpAccount(msg.sender), "MockModule: caller is not ipAccount"); 29 | return param; 30 | } 31 | 32 | function executeNoReturn(string memory) external view { 33 | require(ipAccountRegistry.isIpAccount(msg.sender), "MockModule: caller is not ipAccount"); 34 | } 35 | 36 | function callAnotherModule(string memory moduleName) external returns (string memory) { 37 | require(ipAccountRegistry.isIpAccount(payable(msg.sender)), "MockModule: caller is not ipAccount"); 38 | address moduleAddress = moduleRegistry.getModule(moduleName); 39 | bytes memory output = IIPAccount(payable(msg.sender)).execute( 40 | moduleAddress, 41 | 0, 42 | abi.encodeWithSignature("executeSuccessfully(string)", moduleName) 43 | ); 44 | return abi.decode(output, (string)); 45 | } 46 | 47 | function executeRevert() external pure { 48 | revert("MockModule: executeRevert"); 49 | } 50 | 51 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 52 | return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/foundry/mocks/policy/MockExternalRoyaltyPolicy1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165, ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | 6 | // solhint-disable-next-line max-line-length 7 | import { IExternalRoyaltyPolicy } from "../../../../contracts/interfaces/modules/royalty/policies/IExternalRoyaltyPolicy.sol"; 8 | 9 | contract MockExternalRoyaltyPolicy1 is ERC165, IExternalRoyaltyPolicy { 10 | function getPolicyRtsRequiredToLink(address ipId, uint32 licensePercent) external view returns (uint32) { 11 | return licensePercent * 2; 12 | } 13 | 14 | /// @notice IERC165 interface support 15 | function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) { 16 | return interfaceId == this.getPolicyRtsRequiredToLink.selector || super.supportsInterface(interfaceId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/foundry/mocks/policy/MockExternalRoyaltyPolicy2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165, ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | 6 | // solhint-disable-next-line max-line-length 7 | import { IExternalRoyaltyPolicy } from "../../../../contracts/interfaces/modules/royalty/policies/IExternalRoyaltyPolicy.sol"; 8 | 9 | contract MockExternalRoyaltyPolicy2 is ERC165, IExternalRoyaltyPolicy { 10 | function getPolicyRtsRequiredToLink(address ipId, uint32 licensePercent) external view returns (uint32) { 11 | return 10 * 10 ** 6; 12 | } 13 | 14 | /// @notice IERC165 interface support 15 | function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) { 16 | return interfaceId == this.getPolicyRtsRequiredToLink.selector || super.supportsInterface(interfaceId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/foundry/mocks/policy/MockExternalRoyaltyPolicy3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC165, ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | 6 | // solhint-disable-next-line max-line-length 7 | import { IExternalRoyaltyPolicy } from "../../../../contracts/interfaces/modules/royalty/policies/IExternalRoyaltyPolicy.sol"; 8 | 9 | contract MockExternalRoyaltyPolicy3 is ERC165, IExternalRoyaltyPolicy { 10 | function getPolicyRtsRequiredToLink(address ipId, uint32 licensePercent) external view returns (uint32) { 11 | return 100 * 10 ** 6; 12 | } 13 | 14 | /// @notice IERC165 interface support 15 | function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) { 16 | return interfaceId == this.getPolicyRtsRequiredToLink.selector || super.supportsInterface(interfaceId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/foundry/mocks/policy/MockRoyaltyPolicyLAP.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IRoyaltyModule } from "../../../../contracts/interfaces/modules/royalty/IRoyaltyModule.sol"; 5 | import { IDisputeModule } from "../../../../contracts/interfaces/modules/dispute/IDisputeModule.sol"; 6 | // solhint-disable-next-line max-line-length 7 | import { IGraphAwareRoyaltyPolicy } from "../../../../contracts/interfaces/modules/royalty/policies/IGraphAwareRoyaltyPolicy.sol"; 8 | 9 | contract MockRoyaltyPolicyLAP is IGraphAwareRoyaltyPolicy { 10 | struct RoyaltyPolicyLAPStorage { 11 | mapping(address ipId => uint32) royaltyStack; 12 | } 13 | 14 | bytes32 private constant RoyaltyPolicyLAPStorageLocation = 15 | 0x0c915ba68e2c4e37f19454bb13066f18f9db418fcefbf3c585b4b7d0fb0e0600; 16 | 17 | uint256 public constant MAX_PARENTS = 100; 18 | uint256 public constant MAX_ANCESTORS = 100; 19 | address public constant LICENSING_MODULE = address(0); 20 | address public constant IP_GRAPH = address(0); 21 | IRoyaltyModule public constant ROYALTY_MODULE = IRoyaltyModule(address(0)); 22 | IDisputeModule public constant DISPUTE_MODULE = IDisputeModule(address(0)); 23 | 24 | constructor() {} 25 | 26 | function onLicenseMinting(address ipId, uint32 licensePercent, bytes calldata externalData) external {} 27 | 28 | function onLinkToParents( 29 | address ipId, 30 | address[] calldata parentIpIds, 31 | address[] calldata ancestorsRules, 32 | uint32[] memory licensesPercent, 33 | bytes calldata externalData 34 | ) external returns (uint32) {} 35 | 36 | function getPolicyRtsRequiredToLink(address ipId, uint32 licensePercent) external view returns (uint32) {} 37 | function getPolicyRoyaltyStack(address ipId) external view returns (uint32) { 38 | return _getRoyaltyPolicyLAPStorage().royaltyStack[ipId]; 39 | } 40 | function collectRoyaltyTokens(address ipId, address ancestorIpId) external {} 41 | function claimBySnapshotBatchAsSelf(uint256[] memory snapshotIds, address token, address targetIpId) external {} 42 | function unclaimedRoyaltyTokens(address ipId) external view returns (uint32) {} 43 | function isCollectedByAncestor(address ipId, address ancestorIpId) external view returns (bool) {} 44 | function revenueTokenBalances(address ipId, address token) external view returns (uint256) {} 45 | function snapshotsClaimed(address ipId, address token, uint256 snapshot) external view returns (bool) {} 46 | function snapshotsClaimedCounter(address ipId, address token) external view returns (uint256) {} 47 | function transferToVault(address ipId, address ancestorIpId, address token) external returns (uint256) {} 48 | function getPolicyRoyalty(address ipId, address ancestorIpId) external view returns (uint32) {} 49 | function getAncestorPercent(address ipId, address ancestorIpId) external view returns (uint32) {} 50 | function getTransferredTokens(address ipId, address ancestorIpId, address token) external view returns (uint256) {} 51 | function isSupportGroup() external view returns (bool) { 52 | return false; 53 | } 54 | 55 | function _getRoyaltyPolicyLAPStorage() private pure returns (RoyaltyPolicyLAPStorage storage $) { 56 | assembly { 57 | $.slot := RoyaltyPolicyLAPStorageLocation 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/foundry/mocks/token/MockERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSDL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 5 | 6 | contract MockERC1155 is ERC1155 { 7 | constructor(string memory uri) ERC1155(uri) {} 8 | 9 | function mintId(address to, uint256 tokenId, uint256 value) public returns (uint256) { 10 | _mint(to, tokenId, value, ""); 11 | return tokenId; 12 | } 13 | 14 | function burn(address from, uint256 tokenId, uint256 value) public { 15 | _burn(from, tokenId, value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/foundry/mocks/token/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | constructor() ERC20("MockERC20", "MERC20") {} 8 | 9 | function mint(address to, uint256 amount) external { 10 | _mint(to, amount); 11 | } 12 | 13 | function burn(address from, uint256 amount) external { 14 | _burn(from, amount); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/foundry/mocks/token/MockERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSDL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | import { ERC721URIStorage } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 7 | 8 | contract MockERC721 is ERC721URIStorage { 9 | uint256 private _counter; 10 | 11 | constructor(string memory name) ERC721(name, name) { 12 | _counter = 0; 13 | } 14 | 15 | function mint(address to) public returns (uint256 tokenId) { 16 | tokenId = ++_counter; 17 | _safeMint(to, tokenId); 18 | return tokenId; 19 | } 20 | 21 | function mintId(address to, uint256 tokenId) public returns (uint256) { 22 | _safeMint(to, tokenId); 23 | return tokenId; 24 | } 25 | 26 | function burn(uint256 tokenId) public { 27 | _burn(tokenId); 28 | } 29 | 30 | function transferFrom(address from, address to, uint256 tokenId) public override(ERC721, IERC721) { 31 | _transfer(from, to, tokenId); 32 | } 33 | 34 | function setTokenURI(uint256 tokenId, string memory tokenURI) public { 35 | if (msg.sender != ownerOf(tokenId)) { 36 | revert("caller is not the owner of the token"); 37 | } 38 | _setTokenURI(tokenId, tokenURI); 39 | } 40 | 41 | function _baseURI() internal view virtual override returns (string memory) { 42 | return "https://storyprotocol.xyz/erc721/"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/foundry/mocks/token/MockERC721WithoutMetadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSDL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IERC721, IERC165 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | 6 | contract MockERC721WithoutMetadata is IERC721 { 7 | mapping(uint256 => address) private _owners; 8 | 9 | function mint(address to, uint256 tokenId) external { 10 | _owners[tokenId] = to; 11 | } 12 | 13 | function balanceOf(address owner) external view returns (uint256 balance) { 14 | revert("MockERC721WithoutMetadata: not implemented"); 15 | } 16 | function ownerOf(uint256 tokenId) external view returns (address owner) { 17 | return _owners[tokenId]; 18 | } 19 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external { 20 | revert("MockERC721WithoutMetadata: not implemented"); 21 | } 22 | function safeTransferFrom(address from, address to, uint256 tokenId) external { 23 | revert("MockERC721WithoutMetadata: not implemented"); 24 | } 25 | function transferFrom(address from, address to, uint256 tokenId) external { 26 | revert("MockERC721WithoutMetadata: not implemented"); 27 | } 28 | function approve(address to, uint256 tokenId) external { 29 | revert("MockERC721WithoutMetadata: not implemented"); 30 | } 31 | function setApprovalForAll(address operator, bool approved) external { 32 | revert("MockERC721WithoutMetadata: not implemented"); 33 | } 34 | function getApproved(uint256 tokenId) external view returns (address operator) { 35 | revert("MockERC721WithoutMetadata: not implemented"); 36 | } 37 | function isApprovedForAll(address owner, address operator) external view returns (bool) { 38 | revert("MockERC721WithoutMetadata: not implemented"); 39 | } 40 | function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { 41 | return interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC721).interfaceId; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/foundry/mocks/token/SUSD.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.23; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract SUSD is ERC20 { 7 | constructor() ERC20("Story USD", "SUSD") {} 8 | 9 | function mint(address to, uint256 amount) external { 10 | _mint(to, amount); 11 | } 12 | 13 | function burn(address from, uint256 amount) external { 14 | _burn(from, amount); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/foundry/modules/ModuleBase.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; 5 | 6 | import { BaseTest } from "test/foundry/utils/BaseTest.t.sol"; 7 | 8 | /// @title Module Base Test Contract 9 | /// @notice Base contract for testing standard module functionality. 10 | abstract contract ModuleBaseTest is BaseTest { 11 | /// @notice The module SUT. 12 | IModule public baseModule; 13 | 14 | /// @notice Initializes the base module for testing. 15 | function setUp() public virtual override(BaseTest) { 16 | super.setUp(); 17 | 18 | baseModule = IModule(_deployModule()); 19 | } 20 | 21 | /// @notice Tests that the default resolver constructor runs successfully. 22 | function test_Module_Name() public { 23 | assertEq(baseModule.name(), _expectedName()); 24 | } 25 | 26 | /// @dev Deploys the module SUT. 27 | function _deployModule() internal virtual returns (address); 28 | 29 | /// @dev Gets the expected name for the module. 30 | function _expectedName() internal view virtual returns (string memory); 31 | } 32 | -------------------------------------------------------------------------------- /test/foundry/modules/dispute/btt/DisputeModule.setters.tree: -------------------------------------------------------------------------------- 1 | DisputeModule.sol:setters 2 | ├── when allowlisting dispute tag 3 | │ ├── when caller is not protocol manager 4 | │ │ └── it should revert 5 | │ └── when caller is protocol manager 6 | │ ├── when dispute tag is null 7 | │ │ └── it should revert 8 | │ └── when dispute tag is not null 9 | │ ├── it should set the allowlist of dispute tag with `allowed` value 10 | │ └── it should emit an event 11 | ├── when allowlisting arbitration policy 12 | │ ├── when caller is not protocol manager 13 | │ │ └── it should revert 14 | │ └── when caller is protocol manager 15 | │ ├── when arbitration policy does not support IArbitrationPolicy 16 | │ │ └── it should revert 17 | │ └── when arbitration policy supports IArbitrationPolicy 18 | │ ├── it should set the allowlist of arbitration policy with `allowed` value 19 | │ └── it should emit an event 20 | ├── when allowlisting arbitration relayer 21 | │ ├── when caller is not protocol manager 22 | │ │ └── it should revert 23 | │ └── when caller is protocol manager 24 | │ ├── when arbitration policy does not support IArbitrationPolicy 25 | │ │ └── it should revert 26 | │ └── when arbitration policy supports IArbitrationPolicy 27 | │ ├── when relayer is zero address 28 | │ │ └── it should revert 29 | │ └── when relayer is non-zero address 30 | │ ├── it should set the allowlist of arbitration policy with `allowed` value 31 | │ └── it should emit an event 32 | ├── when setting base arbitration policy 33 | │ ├── when caller is not protocol manager 34 | │ │ └── it should revert 35 | │ └── when caller is protocol manager 36 | │ ├── when arbitration policy is not allowlisted in protocol 37 | │ │ └── it should revert 38 | │ └── when arbitration policy is allowlisted in protocol 39 | │ ├── it should set the base policy as the arbitration policy 40 | │ └── it should emit an event 41 | └── when setting arbitration policy for IP 42 | ├── when caller does not have permission for IP 43 | │ └── it should revert 44 | └── when caller has permission for IP 45 | ├── when arbitration policy is not allowlisted in protocol 46 | │ └── it should revert 47 | └── when arbitration policy is allowlisted in protocol 48 | ├── it should set the IP's arbitration policy 49 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/modules/metadata/btt/CoreMetadataModule.tree: -------------------------------------------------------------------------------- 1 | CoreMetadataModule.sol 2 | ├── when updating NFT token URI 3 | │ ├── when caller does not have permission to call for `ipAccount` 4 | │ │ └── it should revert 5 | │ └── when caller has permission to call for `ipAccount` 6 | │ ├── when IP metadata is immutable 7 | │ │ └── it should revert 8 | │ └── when IP metadata is mutable 9 | │ ├── it should set IP's NFT_TOKEN_URI storage to token URI of tokenId 10 | │ ├── it should set IP's NFT_METADATA_HASH storage to parameter NFT metadata hash 11 | │ └── it should emit an event 12 | ├── when setting metadata URI 13 | │ ├── when caller does not have permission to call for `ipAccount` 14 | │ │ └── it should revert 15 | │ └── when caller has permission to call for `ipAccount` 16 | │ ├── when IP metadata is immutable 17 | │ │ └── it should revert 18 | │ └── when IP metadata is mutable 19 | │ ├── it should set IP's METADATA_URI storage to parameter metadat URI 20 | │ ├── it should set IP's METADATA_HASH storage to parameter metadata hash 21 | │ └── it should emit an event 22 | ├── when setting all 23 | │ ├── when caller does not have permission to call for `ipAccount` 24 | │ │ └── it should revert 25 | │ └── when caller has permission to call for `ipAccount` 26 | │ ├── when IP metadata is immutable 27 | │ │ └── it should revert 28 | │ └── when IP metadata is mutable 29 | │ ├── it should set IP's NFT_TOKEN_URI storage to token URI of tokenId 30 | │ ├── it should set IP's NFT_METADATA_HASH storage to parameter NFT metadata hash 31 | │ ├── it should set IP's METADATA_URI storage to parameter metadat URI 32 | │ ├── it should set IP's METADATA_HASH storage to parameter metadata hash 33 | │ └── it should emit events 34 | └── when freezing metadata 35 | ├── when caller does not have permission to call for `ipAccount` 36 | │ └── it should revert 37 | └── when caller has permission to call for `ipAccount` 38 | ├── it should set IP's IMMUTABLE to true 39 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/modules/metadata/btt/CoreMetadataViewModule.tree: -------------------------------------------------------------------------------- 1 | CoreMetadataViewModule.sol 2 | ├── when getting metadata URI 3 | │ └── it should return IP's core metadata storage METADATA_URI 4 | ├── when getting metadata hash 5 | │ └── it should return IP's core metadata storage METADATA_HASH 6 | ├── when getting registration date 7 | │ └── it should return IP's core metadata storage REGISTRATION_DATE 8 | ├── when getting NFT metadata URI 9 | │ ├── when IP core metadat storage NFT_TOKEN_URI is empty 10 | │ │ └── it should return IP's asset registry storage URI 11 | │ └── when IP core metadat storage NFT_TOKEN_URI is not empty 12 | │ └── it should return IP's core metadata storage NFT_TOKEN_URI 13 | ├── when getting NFT metadata hash 14 | │ └── it should return IP's core metadata storage NFT_METADATA_HASH 15 | ├── when getting IP owner 16 | │ └── it should return IP's owner via IPAccount.owner() 17 | └── when getting JSON string 18 | └── it should return correctly formatted JSON of standard NFT metadata -------------------------------------------------------------------------------- /test/foundry/modules/royalty/VaultController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; 5 | import { VaultController } from "../../../../contracts/modules/royalty/policies/VaultController.sol"; 6 | 7 | // contracts 8 | import { IpRoyaltyVault } from "../../../../contracts/modules/royalty/policies/IpRoyaltyVault.sol"; 9 | import { Errors } from "../../../../contracts/lib/Errors.sol"; 10 | 11 | // tests 12 | import { BaseTest } from "../../utils/BaseTest.t.sol"; 13 | 14 | contract TestRoyaltyModule is BaseTest { 15 | function setUp() public override { 16 | super.setUp(); 17 | vm.startPrank(u.admin); 18 | } 19 | 20 | function test_VaultController_setIpRoyaltyVaultBeacon_revert_ZeroIpRoyaltyVaultBeacon() public { 21 | vm.expectRevert(Errors.VaultController__ZeroIpRoyaltyVaultBeacon.selector); 22 | royaltyModule.setIpRoyaltyVaultBeacon(address(0)); 23 | } 24 | 25 | function test_VaultController_setIpRoyaltyVaultBeacon() public { 26 | address beacon = address(0x1); 27 | royaltyModule.setIpRoyaltyVaultBeacon(beacon); 28 | assertEq(royaltyModule.ipRoyaltyVaultBeacon(), beacon); 29 | } 30 | 31 | function test_VaultController_upgradeVaults() public { 32 | address newVault = address(new IpRoyaltyVault(address(1), address(2), address(3), address(4))); 33 | 34 | (bytes32 operationId, uint32 nonce) = protocolAccessManager.schedule( 35 | address(royaltyModule), 36 | abi.encodeCall(VaultController.upgradeVaults, (newVault)), 37 | 0 // earliest time possible, upgraderExecDelay 38 | ); 39 | vm.warp(upgraderExecDelay + 1); 40 | 41 | royaltyModule.upgradeVaults(newVault); 42 | assertEq(UpgradeableBeacon(royaltyModule.ipRoyaltyVaultBeacon()).implementation(), newVault); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/IpRoyaltyVault.claim.tree: -------------------------------------------------------------------------------- 1 | IpRoyaltyVault.sol:claim 2 | ├── when getting claimable revenue 3 | │ ├── when IP of the vault is dispute tagged 4 | │ │ └── it should return 0 5 | │ └── when IP of the vault is not dispute tagged 6 | │ ├── when the account already claimed token at snapshotId 7 | │ │ └── it should return 0 8 | │ └── when the account has not claimed token at snapshotId 9 | │ ├── it should A = (account's RT balance at snapshotId) 10 | │ ├── it should B = (claimable amount of token at snapshotId) 11 | │ ├── it should C = (total RT supply at snapshotId - unclaimed RT supply at snapshotId) 12 | │ └── it should return (A * B) / C 13 | ├── when claiming revenue by token batch 14 | │ ├── it should, for each token, get claimable revenue of msg.sender at snapshotId 15 | │ ├── it should, for each token, skip if claimable revenue is 0 16 | │ ├── it should, for each token, set claimed as true for msg.sender on token at snapshotId 17 | │ ├── it should, for each token, deduct the claimed amount from the token's claim vault amount 18 | │ ├── it should, for each token, transfer the claimed revenue to msg.sender 19 | │ └── it should, for each token, emit an event 20 | ├── when claiming revenue by snapshot batch 21 | │ ├── it should, for each snapshot, get claimable revenue of msg.sender and token 22 | │ ├── it should, for each snapshot, set claimed as true for msg.sender on token at snapshotId 23 | │ ├── it should deduct the claimed amount from the token's claim vault amount 24 | │ ├── it should transfer the claimed revenue of token to msg.sender 25 | │ └── it should emit an event 26 | └── when collecting royalty tokens 27 | ├── when IP of the vault is dispute tagged 28 | │ └── it should revert 29 | └── when IP of the vault is not dispute tagged 30 | ├── when ancestor IP is not a valid ancestor of vault IP 31 | │ └── it should revert 32 | └── when ancestor IP is a valid ancestor of vault IP 33 | ├── when ancestor IP already claimed royalty token from the vault 34 | │ └── it should revert 35 | └── when ancestor IP has not claimed royalty token from the vault 36 | ├── it should transfer royalty tokens to the ancestor IP 37 | ├── it should set collecte as true for the ancestor IP 38 | ├── it should deduct the claimed RT amount from the vault's unclaimed RT 39 | ├── it should collect any accrued revenue at the time of claim 40 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/IpRoyaltyVault.setters.tree: -------------------------------------------------------------------------------- 1 | IpRoyaltyVault.sol:setters 2 | ├── when initializing IpRoyaltyVault 3 | │ ├── it should set IP ID as the argument value 4 | │ ├── it should set the unclaimed royalty tokens to the argument value 5 | │ ├── it should set the snapshot timestamp as current block timestamp 6 | │ ├── it should mint unclaimedTokens amount of RTs to the vault (RTs claimable by parents) 7 | │ ├── it should mint the rest (supply - unclaimedTokens) amount of RTs to the IP (RTs owned by IP) 8 | │ ├── it should initialize ReentrancyGuard 9 | │ ├── it should initialize ERC20Snapshot 10 | │ └── it should initialize ERC20 with the argument name and symbol 11 | └── when adding token to IpRoyaltyVault 12 | ├── when caller is not Royalty Policy LAP 13 | │ └── it should revert 14 | └── when caller is Royalty Policy LAP 15 | ├── when token does not support IERC20 interface 16 | │ └── it should revert 17 | └── when token supports IERC20 interface 18 | ├── it should add token to the set of tokens 19 | ├── it should emit an event 20 | └── it should return true if the value was added to set -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/IpRoyaltyVault.snapshot.tree: -------------------------------------------------------------------------------- 1 | IpRoyaltyVault.sol:snapshot 2 | └── when taking snapshot 3 | ├── when snapshot interval has not passed 4 | │ └── it should revert 5 | └── when snapshot interval has passed 6 | ├── it should set last snapshot timestamp as now 7 | ├── it should save the current unclaimed RT amount 8 | ├── given each token in non-empty token list 9 | │ ├── when token balance of vault is zero 10 | │ │ └── it should remove token from vault token list 11 | │ └── when token balance of vault is non-zero 12 | │ ├── when new revenue is zero 13 | │ │ └── it should skip to next token in list 14 | │ └── when new revenue is non-zero 15 | │ ├── it should save the portion of new revenue owned to ancestor IPs that have not claimed RT 16 | │ ├── it should set the claimable amount for token at snapshotId 17 | │ └── it should save the portion of new revenue claimable of token by RT owners 18 | ├── it should emit an event 19 | └── it should return the new snapshotId -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/RoyaltyModule.callbacks.tree: -------------------------------------------------------------------------------- 1 | RoyaltyModule.sol:callbacks 2 | ├── when calling onLicensingMinting 3 | │ ├── when caller is not licensing module 4 | │ │ └── it should revert 5 | │ └── when caller is licensing module 6 | │ ├── when royalty policy is not in allowlist of royalty policies 7 | │ │ └── it should revert 8 | │ └── when royalty policy is in allowlist 9 | │ ├── when IP's royalty policy is zero or does not match the royalty policy parameter 10 | │ │ └── it should revert 11 | │ └── when IP's royalty policy is non-zero and is equal to the royalty policy paramter 12 | │ └── it should call royalty policy's callback `onLicensingMinting` 13 | └── when calling onLinkToParents 14 | ├── when caller is not licensing module 15 | │ └── it should revert 16 | └── when caller is licensing module 17 | ├── when royalty policy is not in allowlist of royalty policies 18 | │ └── it should revert 19 | └── when royalty policy is in allowlist 20 | ├── when parent IP list is empty 21 | │ └── it should revert 22 | └── when parent IP list is not empty 23 | ├── given any parent IP's royalty policy is non-zero and does not match the royalty policy parameter 24 | │ └── it should revert 25 | └── given all parent IPs are either zero address or is equal to the royalty policy parameter 26 | ├── it should set child IP's royalty policy as the royalty policy parameter 27 | └── it should call royalty policy's callback `onLinkToParents` -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/RoyaltyModule.payment.tree: -------------------------------------------------------------------------------- 1 | RoyaltyModule.sol:payments 2 | ├── when paying royalty on behalf 3 | │ ├── given the payment token is not in allowlist 4 | │ │ └── it should revert 5 | │ ├── given the payer's royalty policy is zero address 6 | │ │ └── it should revert 7 | │ ├── given the payer's royalty policy is not in allowlist 8 | │ │ └── it should revert 9 | │ ├── given the receiver IP is dispute tagged 10 | │ │ └── it should revert 11 | │ ├── given the payer IP is dispute tagged 12 | │ │ └── it should revert 13 | │ └── given the payment token and payer's royalty policy are in allowlist and receiver IP and payer IP are not dispute tagged 14 | │ ├── it should call royalty policy callback `onRoyaltyPayment` with msg.sender as caller 15 | │ └── it should emit an event 16 | └── when paying license minting fee 17 | ├── given the payment token is not in allowlist 18 | │ └── it should revert 19 | ├── given the license royalty policy is zero address 20 | │ └── it should revert 21 | ├── given the license royalty policy is not in allowlist 22 | │ └── it should revert 23 | ├── given the receiver IP is dispute tagged 24 | │ └── it should revert 25 | └── given the payment token and license royalty policy are in allowlist and receiver IP is not dispute tagged 26 | ├── it should call royalty policy callback `onRoyaltyPayment` with payer as caller 27 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/RoyaltyModule.setters.tree: -------------------------------------------------------------------------------- 1 | RoyaltyModule.sol:setters 2 | ├── when setting licensing module 3 | │ ├── when caller is not protocol manager 4 | │ │ └── it should revert 5 | │ └── when caller is protocol manager 6 | │ ├── when address is zero 7 | │ │ └── it should revert 8 | │ └── when address is non-zero 9 | │ ├── when address does not support IModule interface 10 | │ │ └── it should revert 11 | │ └── when address supports IModule interface 12 | │ └── it set the licensing module address 13 | ├── when setting dispute module 14 | │ ├── when caller is not protocol manager 15 | │ │ └── it should revert 16 | │ └── when caller is protocol manager 17 | │ ├── when address is zero 18 | │ │ └── it should revert 19 | │ └── when address is non-zero 20 | │ ├── when address does not support IModule interface 21 | │ │ └── it should revert 22 | │ └── when address supports IModule interface 23 | │ └── it set the dispute module address 24 | ├── when allowlisting royalty policy 25 | │ ├── when caller is not protocol manager 26 | │ │ └── it should revert 27 | │ └── when caller is protocol manager 28 | │ ├── when policy address is zero 29 | │ │ └── it should revert 30 | │ └── when policy address is non-zero 31 | │ ├── it should set the allowlist of (key: policy) -> (value: allowed) 32 | │ └── it should emit an event 33 | └── when allowlisting royalty token 34 | ├── when caller is not protocol manager 35 | │ └── it should revert 36 | └── when caller is protocol manager 37 | ├── when token address is zero 38 | │ └── it should revert 39 | └── when token address is non-zero 40 | ├── it should set the allowlist of (key: token) -> (value: allowed) 41 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/RoyaltyPolicyLAP.initPolicy.tree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storyprotocol/protocol-core-v1/c47f36cc8fc8c569ea388411ec00d16fbf68ec59/test/foundry/modules/royalty/btt/RoyaltyPolicyLAP.initPolicy.tree -------------------------------------------------------------------------------- /test/foundry/modules/royalty/btt/RoyaltyPolicyLAP.tree: -------------------------------------------------------------------------------- 1 | RoyaltyPolicyLAP.sol 2 | ├── when calling onLicenseMinting 3 | │ ├── when caller is not royalty module 4 | │ │ └── it should revert 5 | │ └── when caller is royalty module 6 | │ ├── when the sum of IP's royalty stack and new license royalty exceeds total supply of RT 7 | │ │ └── it should revert 8 | │ └── when the sum of IP's royalty stack and new license royalty is equal to or less than total supply of RT 9 | │ ├── when IP royalty vault address is non-zero 10 | │ │ └── given the list of ancestor addresses is greater than max ancestors cap 11 | │ │ └── it should revert 12 | │ └── when IP royalty vault address is zero 13 | │ └── it should initialize policy with IP and empty list of parent IPs and license data 14 | ├── when calling onLinkToParents 15 | │ ├── when caller is not royalty module 16 | │ │ └── it should revert 17 | │ └── when caller is royalty module 18 | │ ├── when IP is unlinkable to parents 19 | │ │ └── it should revert 20 | │ └── when IP is linkable to parents 21 | │ └── it should initialize policy with 22 | ├── when calling onRoyaltyPayment 23 | │ ├── when caller is not royalty module 24 | │ │ └── it should revert 25 | │ └── when caller is royalty module 26 | │ ├── it should add the payment token to IP's royalty vault 27 | │ └── it should transfer payment token amount from caller to IP's royalty vault 28 | └── when initializing policy 29 | ├── given size of parent IP list is greater than MAX_PARENTS 30 | │ └── it should revert 31 | ├── given the size of parent royalty list does not equal to size of parent IP list 32 | │ └── it should revert 33 | ├── given the new ancestor count is greater than MAX_ANCESTORS 34 | │ └── it should revert 35 | ├── given the new royalty stack is greater than TOTAL_RT_SUPPLY 36 | │ └── it should revert 37 | └── given the validations pass 38 | ├── it should set parent IPs as unlinkable (can't register as derivative) 39 | ├── it should deploy the beacon proxy of IpRoyaltyVault 40 | ├── it should initialize the deployed IpRoyaltyVault 41 | ├── it should save the royalty data for IP 42 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/registries/IPAccountRegistry.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { IPAccountImpl } from "../../../contracts/IPAccountImpl.sol"; 5 | import { IPAccountRegistry } from "../../../contracts/registries/IPAccountRegistry.sol"; 6 | import { Errors } from "contracts/lib/Errors.sol"; 7 | 8 | import { BaseTest } from "../utils/BaseTest.t.sol"; 9 | 10 | contract MockIPAccountRegistry is IPAccountRegistry { 11 | constructor( 12 | address erc6551Registry, 13 | address ipAccountImpl, 14 | address ipAccountImplBeacon 15 | ) IPAccountRegistry(erc6551Registry, ipAccountImpl, ipAccountImplBeacon) {} 16 | } 17 | 18 | contract IPAccountRegistryTest is BaseTest { 19 | uint256 internal chainId = 100; 20 | address internal tokenAddress = address(200); 21 | uint256 internal tokenId = 300; 22 | 23 | function setUp() public override { 24 | super.setUp(); 25 | } 26 | 27 | function test_IPAccountRegistry_registerIpAccount() public { 28 | address ipAccountAddr = ipAssetRegistry.register(chainId, tokenAddress, tokenId); 29 | 30 | address registryComputedAddress = ipAccountRegistry.ipAccount(chainId, tokenAddress, tokenId); 31 | assertEq(ipAccountAddr, registryComputedAddress); 32 | 33 | IPAccountImpl ipAccount = IPAccountImpl(payable(ipAccountAddr)); 34 | 35 | (uint256 chainId_, address tokenAddress_, uint256 tokenId_) = ipAccount.token(); 36 | assertEq(chainId_, chainId); 37 | assertEq(tokenAddress_, tokenAddress); 38 | assertEq(tokenId_, tokenId); 39 | 40 | assertTrue(ipAssetRegistry.isRegistered(ipAccountAddr)); 41 | } 42 | 43 | function test_IPAccountRegistry_constructor_revert() public { 44 | vm.expectRevert(Errors.IPAccountRegistry_ZeroERC6551Registry.selector); 45 | new MockIPAccountRegistry(address(0), address(123), address(456)); 46 | vm.expectRevert(Errors.IPAccountRegistry_ZeroIpAccountImpl.selector); 47 | new MockIPAccountRegistry(address(123), address(0), address(456)); 48 | vm.expectRevert(Errors.IPAccountRegistry_ZeroIpAccountImplBeacon.selector); 49 | new MockIPAccountRegistry(address(123), address(456), address(0)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/foundry/registries/btt/IPAccountRegistry.tree: -------------------------------------------------------------------------------- 1 | IPAccountRegistry.sol 2 | ├── when registering IP Account 3 | │ ├── it should call ERC-6551 registry with SP IP Account impl and salt 4 | │ ├── it should create a SP IP Account 5 | │ └── it should emit an event 6 | └── when checking address of IP Account 7 | ├── when ERC-6551 implementation is incorrect 8 | │ └── it should return unexpected address 9 | ├── when ERC-6551 salt is incorrect 10 | │ └── it should return unexpected address 11 | ├── when chain ID is incorrect 12 | │ └── it should return unexpected address 13 | ├── when token contract is incorrect 14 | │ └── it should return unexpected address 15 | ├── when token ID is incorrect 16 | │ └── it should return unexpected address 17 | └── when all inputs are correct 18 | └── it should return expected address -------------------------------------------------------------------------------- /test/foundry/registries/btt/IPAssetRegistry.tree: -------------------------------------------------------------------------------- 1 | IPAssetRegistry.sol 2 | ├── when registering IP Account 3 | │ ├── when IP already has name set in storage 4 | │ │ └── it should revert 5 | │ └── when IP does not have name set in storage 6 | │ ├── when the chain ID is cross-chain 7 | │ │ ├── it should set the IP name as chainId + tokenContract + tokenId 8 | │ │ ├── it should set the IP uri as empty 9 | │ │ ├── it should set the IP registration date 10 | │ │ ├── it should increment the total supply 11 | │ │ └── it should emit an event 12 | │ └── when the chain ID is local 13 | │ ├── given the token contract does not support IERC721 interface 14 | │ │ └── it should revert 15 | │ ├── given the owner of token ID is zero address (DNE or burned) 16 | │ │ └── it should revert 17 | │ ├── given the token contract does not support IERC721Metadata interface 18 | │ │ └── it should revert 19 | │ └── given the token contract supports IERC721 and IERC721Metadata and token ID exists 20 | │ ├── it should set the IP name as chainId + contract name + tokenId 21 | │ ├── it should set the IP uri as token URI of tokenId 22 | │ ├── it should set the IP registration date 23 | │ ├── it should increment the total supply 24 | │ └── it should emit an event 25 | └── when checking if IP is registered 26 | ├── given the IP ID is zero address 27 | │ └── it should return false 28 | ├── given the IP ID has no code 29 | │ └── it should return false 30 | ├── given the IP ID does not support IIPAccount interface 31 | │ └── it should return false 32 | ├── given the IP ID does not match the expected registered IP ID 33 | │ └── it should return false 34 | ├── given the IP ID does not have name set in storage 35 | │ └── it should return false 36 | └── given the IP ID is valid, matches the expected ID, and has name set in storage 37 | └── it should return true -------------------------------------------------------------------------------- /test/foundry/registries/btt/LicenseRegistry.attachLicenseTermsToIp.tree: -------------------------------------------------------------------------------- 1 | LicenseRegistry.sol:attachLicenseTermsToIp 2 | ├── when caller is not licensing module 3 | │ └── it should revert 4 | └── when caller is licensing module 5 | ├── given the license template is not registered 6 | │ └── it should revert 7 | ├── given the license terms ID is not registered (DNE) 8 | │ └── it should revert 9 | ├── given the IP has parents (is derivative) 10 | │ └── it should revert 11 | ├── given the IP is expired 12 | │ └── it should revert 13 | └── given license template and terms ID are registered, IP is root, and IP is not expired 14 | ├── it should set the license template as IP's license template 15 | ├── it should add the license terms ID to IP's set of attached license terms 16 | └── it should associate the set index of attahced license template with that of attached license terms ID -------------------------------------------------------------------------------- /test/foundry/registries/btt/LicenseRegistry.registerDerivativeIp.tree: -------------------------------------------------------------------------------- 1 | LicenseRegistry.sol:registerDerivativeIp 2 | ├── when caller is not licensing module 3 | │ └── it should revert 4 | └── when caller is licensing module 5 | ├── given the parent IP list is empty 6 | │ └── it should revert 7 | ├── given the length of parent IP list does not equal the length of license term ID list 8 | │ └── it should revert 9 | ├── given the child IP already has license terms attached (not default terms) 10 | │ └── it should revert 11 | ├── given the child IP already has one or more parents (is derivative) 12 | │ └── it should revert 13 | └── given the parent IP list is non-empty and child has no parents and attached license terms 14 | ├── given any parent IP is the child IP 15 | │ └── it should revert 16 | ├── given any parent IP has dispute tag 17 | │ └── it should revert 18 | ├── given any parent IP has expired 19 | │ └── it should revert 20 | └── given all parent IPs are not disputed or expired and are not the child IP 21 | ├── when the license template and terms are not default 22 | │ ├── given any parent IP does not have license template in its set of used license templates 23 | │ │ └── it should revert 24 | │ └── given any parent IP does not have license terms ID in its set of attached license terms ID 25 | │ └── it should revert 26 | └── when all the license terms are either from parent IPs or default license terms 27 | ├── it should, for each parent, add parent IP to the child IP's set of parent IPs 28 | ├── it should, for each parent, add child IP to the parent's set of child IPs 29 | ├── it should, for each license terms, add license terms to child's set of attached license terms 30 | ├── it should add license template to the child IP's set of license templates 31 | └── it should set the expiry time of child IP -------------------------------------------------------------------------------- /test/foundry/registries/btt/LicenseRegistry.setters.tree: -------------------------------------------------------------------------------- 1 | LicenseRegistry.sol:setters 2 | ├── when setting dispute module 3 | │ ├── when caller is not protocol manager 4 | │ │ └── it should revert 5 | │ └── when caller is protocol manager 6 | │ ├── when address is zero 7 | │ │ └── it should revert 8 | │ └── when address is non-zero 9 | │ ├── when address does not support IModule interface 10 | │ │ └── it should revert 11 | │ └── when address supports IModule interface 12 | │ └── it set the dispute module address 13 | ├── when setting licensing module 14 | │ ├── when caller is not protocol manager 15 | │ │ └── it should revert 16 | │ └── when caller is protocol manager 17 | │ ├── when address is zero 18 | │ │ └── it should revert 19 | │ └── when address is non-zero 20 | │ ├── when address does not support IModule interface 21 | │ │ └── it should revert 22 | │ └── when address supports IModule interface 23 | │ └── it set the licensing module address 24 | ├── when setting defaut license terms 25 | │ ├── when caller is not protocol manager 26 | │ │ └── it should revert 27 | │ └── when caller is protocol manager 28 | │ ├── when default license terms are set 29 | │ │ └── it should revert 30 | │ └── when default license terms are unset 31 | │ ├── it should set the default license template 32 | │ └── it should set the default license terms 33 | ├── when setting expiration time 34 | │ ├── when caller is not licensing module 35 | │ │ └── it should revert 36 | │ └── when caller is licensing module 37 | │ ├── it should set expiration time in IP Account storage 38 | │ └── it should emit an event 39 | ├── when setting minting license config for license 40 | │ ├── when caller is not licensing module 41 | │ │ └── it should revert 42 | │ └── when caller is licensing module 43 | │ ├── when license template is not registered 44 | │ │ └── it should revert 45 | │ └── when license template is registered 46 | │ ├── it should set 47 | │ └── it should emit an event 48 | └── when setting minting license config for IP 49 | ├── when caller is not licensing module 50 | │ └── it should revert 51 | └── when caller is licensing module 52 | ├── it should set the minting license config struct for IP 53 | └── it should emit an event -------------------------------------------------------------------------------- /test/foundry/registries/btt/ModuleRegistry.tree: -------------------------------------------------------------------------------- 1 | ModuleRegistry.sol 2 | ├── when registering module type 3 | │ ├── when caller is not protocol manager 4 | │ │ └── it should revert 5 | │ └── when caller is protocol manager 6 | │ ├── given the name is null 7 | │ │ └── it should revert 8 | │ ├── given the interface ID is null 9 | │ │ └── it should revert 10 | │ └── given the name and interface ID are valid 11 | │ ├── when name is already registered with interface ID 12 | │ │ └── it should revert 13 | │ └── when name is not registered 14 | │ ├── it should set the key `name` with value `interfaceId` 15 | │ └── it should emit an event 16 | ├── when removing module type 17 | │ ├── when caller is not protocol manager 18 | │ │ └── it should revert 19 | │ └── when caller is protocol manager 20 | │ ├── when name is null 21 | │ │ └── it should revert 22 | │ └── when name is not null 23 | │ ├── given the name is not registered with any interface ID 24 | │ │ └── it should revert 25 | │ └── given the name is registered 26 | │ ├── it should delete the interface ID 27 | │ └── it should emit an event 28 | ├── when registering module 29 | │ ├── when caller is not protocol manager 30 | │ │ └── it should revert 31 | │ └── when caller is protocol manager 32 | │ ├── given the module type (interface ID) is null 33 | │ │ └── it should revert 34 | │ ├── given the module type is not registered 35 | │ │ └── it should revert 36 | │ ├── given the module address is zero address 37 | │ │ └── it should revert 38 | │ ├── given the module address is not a contract 39 | │ │ └── it should revert 40 | │ ├── given the module address is already registered (check name) 41 | │ │ └── it should revert 42 | │ ├── given the module is already registered 43 | │ │ └── it should revert 44 | │ ├── given the module does not support interface ID of module type 45 | │ │ └── it should revert 46 | │ └── given the inputs are valid, module is not registered, and type is registered 47 | │ ├── it should add (key: module name) -> (value: module address) 48 | │ ├── it should add (key: module address) -> (value: module type) 49 | │ └── it should emit an event 50 | ├── when removing module 51 | │ ├── when caller is not protocol manager 52 | │ │ └── it should revert 53 | │ └── when caller is protocol manager 54 | │ ├── given the name is null 55 | │ │ └── it should revert 56 | │ ├── given the module with name is not registered 57 | │ │ └── it should revert 58 | │ └── given the module with name is registered 59 | │ ├── it should delete (key: module name) -> (value: module address) 60 | │ ├── it should delete (key: module address) -> (value: module type) 61 | │ └── it should emit an event 62 | └── when checking if module is registered 63 | ├── when the return module type of the module address is zero 64 | │ └── it should return false 65 | └── when the return module type of the module address is non-zero 66 | └── it should return true -------------------------------------------------------------------------------- /test/foundry/utils/TestProxyHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 4 | import { ICreate3Deployer } from "../../../script/foundry/utils/ICreate3Deployer.sol"; 5 | 6 | library TestProxyHelper { 7 | /// Deploys a new UUPS proxy with the provided implementation and data 8 | /// @dev WARNING: DO NOT USE IN PRODUCTION without checking storage layout compatibility 9 | /// @param impl address of the implementation contract 10 | /// @param data encoded initializer call 11 | function deployUUPSProxy(address impl, bytes memory data) internal returns (address) { 12 | ERC1967Proxy proxy = new ERC1967Proxy(impl, data); 13 | return address(proxy); 14 | } 15 | 16 | function deployUUPSProxy( 17 | ICreate3Deployer create3Deployer, 18 | bytes32 salt, 19 | address impl, 20 | bytes memory data 21 | ) internal returns (address) { 22 | return 23 | create3Deployer.deployDeterministic( 24 | abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(impl, data)), 25 | salt 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/foundry/utils/Users.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.26; 3 | 4 | import { Vm } from "forge-std/Vm.sol"; 5 | 6 | struct Users { 7 | // Default admin 8 | address payable admin; 9 | // Random users 10 | address payable alice; 11 | address payable bob; 12 | address payable carl; 13 | address payable dan; 14 | // Malicious user 15 | address payable eve; 16 | // Relayer 17 | address payable relayer; 18 | } 19 | 20 | library UsersLib { 21 | function createUser(string memory name, Vm vm) public returns (address payable user) { 22 | user = payable(address(uint160(uint256(keccak256(abi.encodePacked(name)))))); 23 | vm.deal(user, 100 ether); // set balance to 100 ether 24 | vm.label(user, name); 25 | return user; 26 | } 27 | 28 | function createMockUsers(Vm vm) public returns (Users memory) { 29 | return 30 | Users({ 31 | // admin: payable(address(123)), // same as AccessControlHelper.sol (123 => 0x7b) 32 | admin: createUser("Admin", vm), 33 | alice: createUser("Alice", vm), 34 | bob: createUser("Bob", vm), 35 | carl: createUser("Carl", vm), 36 | dan: createUser("Dan", vm), 37 | eve: createUser("Eve", vm), 38 | relayer: createUser("Relayer", vm) 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/hardhat/README.md: -------------------------------------------------------------------------------- 1 | # Story Protocol End-to-End Testing 2 | 3 | This folder contains story protocol end-to-end test scripts. 4 | 5 | ## Requirements 6 | 7 | Please install the following: 8 | 9 | - [Foundry / Foundryup](https://github.com/gakonst/foundry) 10 | - [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started#overview) 11 | 12 | ## Quickstart 13 | 14 | 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. 15 | 16 | ```sh 17 | yarn # this installs packages 18 | ``` 19 | 20 | You'll need to add the variables refer to the .env.example to a .env file at project root. 21 | 22 | Then, at project root run the tests with command: 23 | 24 | ```sh 25 | npx hardhat test --network odyssey 26 | ``` 27 | 28 | You can specify the file path if you want to run test on a specific file: 29 | 30 | ```sh 31 | npx hardhat test [file-path] --network odyssey 32 | ``` 33 | -------------------------------------------------------------------------------- /test/hardhat/e2e/constants.ts: -------------------------------------------------------------------------------- 1 | // This is the deployed protocol address constants file. 2 | 3 | export const AccessController = "0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a"; 4 | export const ArbitrationPolicyUMA = "0xfFD98c3877B8789124f02C7E8239A4b0Ef11E936"; 5 | export const CoreMetadataModule = "0x6E81a25C99C6e8430aeC7353325EB138aFE5DC16"; 6 | export const CoreMetadataViewModule = "0xB3F88038A983CeA5753E11D144228Ebb5eACdE20"; 7 | export const DisputeModule = "0x9b7A9c70AFF961C799110954fc06F3093aeb94C5"; 8 | export const EvenSplitGroupPool = "0xf96f2c30b41Cb6e0290de43C8528ae83d4f33F89"; 9 | export const GroupNFT = "0x4709798FeA84C84ae2475fF0c25344115eE1529f"; 10 | export const GroupingModule = "0x69D3a7aa9edb72Bc226E745A7cCdd50D947b69Ac"; 11 | export const IPAccountImpl = "0xc93d49fEdED1A2fbE3B54223Df65f4edB3845eb0"; 12 | export const IPAssetRegistry = "0x77319B4031e6eF1250907aa00018B8B1c67a244b"; 13 | export const IPGraphACL = "0x1640A22a8A086747cD377b73954545e2Dfcc9Cad"; 14 | export const IpRoyaltyVaultBeacon = "0x6928ba25Aa5c410dd855dFE7e95713d83e402AA6"; 15 | export const IpRoyaltyVaultImpl = "0x63cC7611316880213f3A4Ba9bD72b0EaA2010298"; 16 | export const LicenseRegistry = "0x529a750E02d8E2f15649c13D69a465286a780e24"; 17 | export const LicenseToken = "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC"; 18 | export const LicensingModule = "0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f"; 19 | // export const MockERC20 = "0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E"; 20 | export const MockERC20 = "0x1514000000000000000000000000000000000000"; 21 | export const ModuleRegistry = "0x022DBAAeA5D8fB31a0Ad793335e39Ced5D631fa5"; 22 | export const PILicenseTemplate = "0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316"; 23 | export const ProtocolAccessManager = "0xFdece7b8a2f55ceC33b53fd28936B4B1e3153d53"; 24 | export const ProtocolPauseAdmin = "0xdd661f55128A80437A0c0BDA6E13F214A3B2EB24"; 25 | export const RoyaltyModule = "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086"; 26 | export const RoyaltyPolicyLAP = "0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E"; 27 | export const RoyaltyPolicyLRP = "0x9156e603C949481883B1d3355c6f1132D191fC41"; 28 | 29 | // Mock ERC721 contract address 30 | export const MockERC721 = process.env.STORY_ERC721 as string; 31 | export const STORY_OOV3 = process.env.STORY_OOV3 as string; 32 | -------------------------------------------------------------------------------- /test/hardhat/e2e/grouping/group.authority.test.ts: -------------------------------------------------------------------------------- 1 | // Test: Group Authorization 2 | 3 | import { EvenSplitGroupPool } from "../constants"; 4 | import "../setup" 5 | import { expect } from "chai" 6 | 7 | describe("Grouping Module Authorization", function () { 8 | it("Non-admin whitelist group reward pool", async function () { 9 | await expect( 10 | this.groupingModule.connect(this.user1).whitelistGroupRewardPool(EvenSplitGroupPool, false) 11 | ).to.be.rejectedWith(Error).then((error) => { console.log(JSON.stringify(error, null, 2)), expect(error.data).to.contain("0x068ca9d8") }); 12 | 13 | const isWhitelisted = await this.ipAssetRegistry.isWhitelistedGroupRewardPool(EvenSplitGroupPool); 14 | expect(isWhitelisted).to.be.true; 15 | }); 16 | 17 | it("Admin whitelist group reward pool", async function () { 18 | await expect( 19 | this.groupingModule.whitelistGroupRewardPool(EvenSplitGroupPool, false) 20 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); 21 | 22 | let isWhitelisted = await this.ipAssetRegistry.isWhitelistedGroupRewardPool(EvenSplitGroupPool); 23 | expect(isWhitelisted).to.be.false; 24 | 25 | await expect( 26 | this.groupingModule.whitelistGroupRewardPool(EvenSplitGroupPool, true) 27 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); 28 | 29 | isWhitelisted = await this.ipAssetRegistry.isWhitelistedGroupRewardPool(EvenSplitGroupPool); 30 | expect(isWhitelisted).to.be.true; 31 | }); 32 | 33 | it("Admin whitelist invalid group reward pool", async function () { 34 | const invalidGroupPool = "0xDA5b9f185ac6b5b61BF84892d94BF1826984dA5A"; 35 | await expect( 36 | this.groupingModule.whitelistGroupRewardPool(invalidGroupPool, true) 37 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); 38 | 39 | let isWhitelisted = await this.ipAssetRegistry.isWhitelistedGroupRewardPool(invalidGroupPool); 40 | expect(isWhitelisted).to.be.true; 41 | 42 | await expect( 43 | this.groupingModule.whitelistGroupRewardPool(invalidGroupPool, false) 44 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); 45 | 46 | isWhitelisted = await this.ipAssetRegistry.isWhitelistedGroupRewardPool(invalidGroupPool); 47 | expect(isWhitelisted).to.be.false; 48 | }); 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /test/hardhat/e2e/ipa/ipa.test.ts: -------------------------------------------------------------------------------- 1 | // Test: IP Asset 2 | 3 | import "../setup" 4 | import { expect } from "chai" 5 | import { mintNFT } from "../utils/nftHelper" 6 | import hre from "hardhat"; 7 | import { MockERC721 } from "../constants"; 8 | import { mintNFTAndRegisterIPA } from "../utils/mintNFTAndRegisterIPA"; 9 | 10 | describe("IP Asset", function () { 11 | let signers:any; 12 | 13 | this.beforeAll("Get Signers", async function () { 14 | // Get the signers 15 | signers = await hre.ethers.getSigners(); 16 | }) 17 | 18 | it("NFT owner register IP Asset with an NFT token", async function () { 19 | const tokenId = await mintNFT(signers[0]); 20 | const connectedRegistry = this.ipAssetRegistry.connect(signers[0]); 21 | 22 | const ipId = await expect( 23 | connectedRegistry.register(this.chainId, MockERC721, tokenId) 24 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]); 25 | console.log("ipId:", ipId); 26 | 27 | expect(ipId).to.not.be.empty.and.to.be.a("HexString"); 28 | 29 | const isRegistered = await expect( 30 | connectedRegistry.isRegistered(ipId) 31 | ).not.to.be.rejectedWith(Error); 32 | 33 | expect(isRegistered).to.equal(true); 34 | }); 35 | 36 | it("Non-NFT owner register IP asset with an NFT token", async function () { 37 | const tokenId = await mintNFT(signers[0]); 38 | const connectedRegistry = this.ipAssetRegistry.connect(signers[1]); 39 | 40 | const ipId = await expect( 41 | connectedRegistry.register(this.chainId, MockERC721, tokenId) 42 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]); 43 | console.log("ipId:", ipId); 44 | 45 | expect(ipId).to.not.be.empty.and.to.be.a("HexString"); 46 | 47 | const isRegistered = await expect( 48 | connectedRegistry.isRegistered(ipId) 49 | ).not.to.be.rejectedWith(Error); 50 | 51 | expect(isRegistered).to.equal(true); 52 | }); 53 | 54 | it("Register IP asset, the caller doesn’t have enough IP token", async function () { 55 | const tokenId = await mintNFT(signers[0]); 56 | 57 | // generate random wallet 58 | const randomWallet = hre.ethers.Wallet.createRandom(); 59 | const randomSigner = randomWallet.connect(hre.ethers.provider); 60 | const connectedRegistry = this.ipAssetRegistry.connect(randomSigner); 61 | 62 | await expect( 63 | connectedRegistry.register(this.chainId, MockERC721, tokenId) 64 | ).to.be.rejectedWith(`insufficient funds`, `"code": -32000, "message": "insufficient funds for gas * price + value: balance 0`); 65 | }); 66 | 67 | it("Check the default license after registering IP asset", async function () { 68 | console.log("============ Register IPA ============"); 69 | const { ipId } = await mintNFTAndRegisterIPA(); 70 | 71 | console.log("============ Get Default License Terms ============"); 72 | const { licenseTermsId: defaultId } = await this.licenseRegistry.getDefaultLicenseTerms(); 73 | console.log("defaultId", defaultId); 74 | 75 | console.log("============ Get Attached License Terms ============"); 76 | expect(await this.licenseRegistry.getAttachedLicenseTermsCount(ipId)).to.be.equal(0); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/hardhat/e2e/ipa/metadata.test.ts: -------------------------------------------------------------------------------- 1 | // Test: IP Asset Metadata 2 | 3 | import "../setup" 4 | import { mintNFTAndRegisterIPA } from "../utils/mintNFTAndRegisterIPA"; 5 | import { encodeBytes32String } from "ethers"; 6 | import { expect } from "chai"; 7 | 8 | describe("IP Asset Set Metadata", function () { 9 | let ipId: string; 10 | before("Register IPA", async function () { 11 | console.log(`================= Register IPA =================`); 12 | ({ ipId } = await mintNFTAndRegisterIPA()); 13 | }); 14 | 15 | it("Update NFT token URI", async function() { 16 | console.log(`================= Update NFT token URI =================`); 17 | const newNFTMetadataHash = encodeBytes32String("test-nft-metadata-hash"); 18 | await expect( 19 | this.coreMetadataModule.updateNftTokenURI(ipId, newNFTMetadataHash) 20 | ).not.to.be.rejectedWith(Error).then((tx: any) => tx.wait()); 21 | 22 | console.log(`================= Get NftMetadataHash =================`); 23 | const nftMetadataHash = await expect( 24 | this.CoreMetadataViewModule.getNftMetadataHash(ipId) 25 | ).not.to.be.rejectedWith(Error); 26 | expect(nftMetadataHash).to.equal(nftMetadataHash); 27 | }); 28 | 29 | it("Set Metadata URI", async function() { 30 | console.log(`================= Set Metadata URI =================`); 31 | const newMetaURI = "https://ipfs/bafkreiguk57p"; 32 | const newMetaHash = encodeBytes32String("test-metadata-hash"); 33 | await expect( 34 | this.coreMetadataModule.setMetadataURI(ipId, newMetaURI, newMetaHash) 35 | ).not.to.be.rejectedWith(Error).then((tx: any) => tx.wait()); 36 | 37 | console.log(`================= Get MetadataHash =================`); 38 | const metadataHash = await expect( 39 | this.CoreMetadataViewModule.getMetadataHash(ipId) 40 | ).not.to.be.rejectedWith(Error); 41 | expect(metadataHash).to.equal(newMetaHash); 42 | 43 | console.log(`================= Get MetadataURI =================`); 44 | const metadataURI = await expect( 45 | this.CoreMetadataViewModule.getMetadataURI(ipId) 46 | ).not.to.be.rejectedWith(Error); 47 | expect(metadataURI).to.equal(newMetaURI); 48 | }); 49 | 50 | it("Update metadata after freeze metadata", async function() { 51 | console.log(`================= Freeze metadata =================`); 52 | await expect( 53 | this.coreMetadataModule.freezeMetadata(ipId) 54 | ).not.to.be.rejectedWith(Error).then((tx: any) => tx.wait()); 55 | 56 | console.log(`================= Update metadata after freeze metadata =================`); 57 | const newMetaURI = "https://ipfs/bafkreiguk99p"; 58 | const newMetaHash = encodeBytes32String("test-metadata-hash-freeze"); 59 | await expect( 60 | this.coreMetadataModule.setMetadataURI(ipId, newMetaURI, newMetaHash) 61 | ).to.be.revertedWithCustomError(this.errors, "CoreMetadataModule__MetadataAlreadyFrozen"); 62 | 63 | console.log(`================= Update metadata after freeze metadata =================`); 64 | const newNFTMetadataHash = encodeBytes32String("test-nft-metadata-hash-freeze"); 65 | await expect( 66 | this.coreMetadataModule.updateNftTokenURI(ipId, newNFTMetadataHash) 67 | ).to.be.revertedWithCustomError(this.errors, "CoreMetadataModule__MetadataAlreadyFrozen"); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/hardhat/e2e/license/attachLicenseTerms.test.ts: -------------------------------------------------------------------------------- 1 | // Test: LicensingModule - attachLicenseTerms, attachDefaultLicenseTerms 2 | 3 | import "../setup"; 4 | import { expect } from "chai"; 5 | import hre from "hardhat"; 6 | import { MockERC721, PILicenseTemplate } from "../constants"; 7 | import { mintNFT } from "../utils/nftHelper"; 8 | 9 | describe("LicensingModule - attachLicenseTerms", function () { 10 | let signers: any; 11 | 12 | this.beforeAll("Get Signers", async function () { 13 | // Get the signers 14 | signers = await hre.ethers.getSigners(); 15 | }); 16 | 17 | it("IP Asset attach a license except for default one", async function () { 18 | const tokenId = await mintNFT(signers[0]); 19 | const connectedRegistry = this.ipAssetRegistry.connect(signers[0]); 20 | 21 | const ipId = await expect( 22 | connectedRegistry.register(this.chainId, MockERC721, tokenId) 23 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]); 24 | console.log("ipId:", ipId); 25 | expect(ipId).to.not.be.empty.and.to.be.a("HexString"); 26 | 27 | const connectedLicensingModule = this.licensingModule.connect(signers[0]); 28 | console.log(this.nonCommercialLicenseId); 29 | 30 | const attachLicenseTx = await expect( 31 | connectedLicensingModule.attachLicenseTerms(ipId, PILicenseTemplate, this.commercialUseLicenseId) 32 | ).not.to.be.rejectedWith(Error); 33 | await attachLicenseTx.wait(); 34 | console.log(attachLicenseTx.hash); 35 | expect(attachLicenseTx.hash).to.not.be.empty.and.to.be.a("HexString"); 36 | }); 37 | }); 38 | 39 | describe("LicensingModule - attachDefaultLicenseTerms", function () { 40 | let signers: any; 41 | let ipId: string; 42 | 43 | this.beforeAll("Get Signers", async function () { 44 | // Get the signers 45 | signers = await hre.ethers.getSigners(); 46 | 47 | const tokenId = await mintNFT(signers[0]); 48 | const connectedRegistry = this.ipAssetRegistry.connect(signers[0]); 49 | 50 | ipId = await expect( 51 | connectedRegistry.register(this.chainId, MockERC721, tokenId) 52 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]); 53 | console.log("ipId:", ipId); 54 | }); 55 | 56 | it("IP Asset attach the default license terms", async function () { 57 | const connectedLicensingModule = this.licensingModule.connect(signers[0]); 58 | 59 | const attachLicenseTx = await expect( 60 | connectedLicensingModule.attachDefaultLicenseTerms(ipId) 61 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()); 62 | expect(attachLicenseTx.hash).to.not.be.empty.and.to.be.a("HexString"); 63 | }); 64 | 65 | it("IP Asset attach the default license terms again", async function () { 66 | const connectedLicensingModule = this.licensingModule.connect(signers[0]); 67 | 68 | await expect( 69 | connectedLicensingModule.attachDefaultLicenseTerms(ipId) 70 | ).to.be.revertedWithCustomError(this.errors, "LicenseRegistry__LicenseTermsAlreadyAttached"); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/hardhat/e2e/licenseTermsTemplate.ts: -------------------------------------------------------------------------------- 1 | // This file is used to define the terms of a license that will be used in the tests. 2 | 3 | import hre from "hardhat"; 4 | 5 | export const terms = { 6 | transferable: true, 7 | royaltyPolicy: hre.ethers.ZeroAddress, 8 | defaultMintingFee: 0, 9 | expiration: 0, 10 | commercialUse: false, 11 | commercialAttribution: false, 12 | commercializerChecker: hre.ethers.ZeroAddress, 13 | commercializerCheckerData: hre.ethers.ZeroAddress, 14 | commercialRevShare: 0, 15 | commercialRevCeiling: 0, 16 | derivativesAllowed: true, 17 | derivativesAttribution: false, 18 | derivativesApproval: false, 19 | derivativesReciprocal: false, 20 | derivativeRevCeiling: 0, 21 | currency: hre.ethers.ZeroAddress, 22 | uri: "", 23 | }; -------------------------------------------------------------------------------- /test/hardhat/e2e/permission/permission.test.ts: -------------------------------------------------------------------------------- 1 | // Test: Permission 2 | 3 | import "../setup" 4 | import { expect } from "chai" 5 | import { mintNFT } from "../utils/nftHelper" 6 | import hre from "hardhat"; 7 | import { LicensingModule, MockERC721 } from "../constants"; 8 | 9 | describe("Permission", function () { 10 | let signers:any; 11 | 12 | this.beforeAll("Get Signers", async function () { 13 | // Get the signers 14 | signers = await hre.ethers.getSigners(); 15 | console.log("signers:", signers[0].address); 16 | }) 17 | 18 | it("Add a new ALLOW permission of IP asset for a signer and change the permission to DENY", async function () { 19 | const tokenId = await mintNFT(signers[0]); 20 | const connectedRegistry = this.ipAssetRegistry.connect(signers[0]); 21 | const func = hre.ethers.encodeBytes32String("attachLicenseTerms").slice(0, 10); 22 | const ALLOW_permission = 1; 23 | const DENY_permission = 2; 24 | let permissionAfter: number; 25 | let result: any; 26 | 27 | const connecedAccessController = this.accessController.connect(signers[0]); 28 | 29 | // add ALLOW permission for invalid ipId 30 | await expect( 31 | connecedAccessController.setPermission("0x0", signers[0].address, LicensingModule, func, ALLOW_permission) 32 | ).to.be.rejectedWith(Error); 33 | 34 | // add DENY permission for invalid ipId 35 | await expect( 36 | connecedAccessController.setPermission("0x0", signers[0].address, LicensingModule, func, DENY_permission) 37 | ).to.be.rejectedWith(Error); 38 | 39 | const ipId = await expect( 40 | connectedRegistry.register(this.chainId, MockERC721, tokenId) 41 | ).not.to.be.rejectedWith(Error).then((tx) => tx.wait()).then((receipt) => receipt.logs[2].args[0]); 42 | console.log("ipId:", ipId); 43 | expect(ipId).to.not.be.empty.and.to.be.a("HexString"); 44 | 45 | const permissionBefore = await connecedAccessController.getPermission(ipId, signers[0].address, LicensingModule, func); 46 | expect(permissionBefore).to.equal(0); 47 | 48 | // add ALLOW permission 49 | result = await connecedAccessController.setPermission(ipId, signers[0].address, LicensingModule, func, ALLOW_permission); 50 | expect(result.hash).to.not.be.empty.and.to.be.a("HexString"); 51 | await result.wait(); 52 | 53 | // check the permission 54 | permissionAfter = await connecedAccessController.getPermission(ipId, signers[0].address, LicensingModule, func); 55 | expect(permissionAfter).to.equal(ALLOW_permission); 56 | 57 | // Change to DENY permission 58 | result = await connecedAccessController.setPermission(ipId, signers[0].address, LicensingModule, func, DENY_permission); 59 | expect(result.hash).to.not.be.empty.and.to.be.a("HexString"); 60 | await result.wait(); 61 | 62 | // check the permission 63 | permissionAfter = await connecedAccessController.getPermission(ipId, signers[0].address, LicensingModule, func); 64 | expect(permissionAfter).to.equal(DENY_permission); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/hardhat/e2e/utils/licenseHelper.ts: -------------------------------------------------------------------------------- 1 | // Purpose: Helper functions for licensing config and registering PIL terms functions 2 | 3 | import hre from "hardhat"; 4 | import { EvenSplitGroupPool, MockERC20, PILicenseTemplate } from "../constants"; 5 | import { terms } from "../licenseTermsTemplate"; 6 | 7 | export const LicensingConfig = ({ 8 | isSet: true, 9 | mintingFee: 0, 10 | licensingHook: hre.ethers.ZeroAddress, 11 | hookData: "0x", 12 | commercialRevShare: 10 * 10 ** 6, 13 | disabled: false, 14 | expectMinimumGroupRewardShare: 0, 15 | expectGroupRewardPool: EvenSplitGroupPool, 16 | }); 17 | 18 | export async function registerPILTerms 19 | ( 20 | commercialUse: boolean = false, 21 | mintingFee: number = 0, 22 | commercialRevShare: number = 0, 23 | royaltyPolicy: string = hre.ethers.ZeroAddress, 24 | expiration: number = 0, 25 | currencyToken: string = MockERC20, 26 | derivativesReciprocal: boolean = true, 27 | derivativesApproval: boolean = false, 28 | ): Promise { 29 | 30 | const licenseTemplate = await hre.ethers.getContractAt("PILicenseTemplate", PILicenseTemplate); 31 | 32 | const testTerms = { ...terms }; 33 | testTerms.royaltyPolicy = royaltyPolicy; 34 | testTerms.defaultMintingFee = mintingFee; 35 | testTerms.commercialUse = commercialUse; 36 | testTerms.commercialRevShare = commercialRevShare; 37 | testTerms.currency = currencyToken; 38 | testTerms.expiration = expiration; 39 | testTerms.derivativesReciprocal = derivativesReciprocal; 40 | testTerms.derivativesApproval = derivativesApproval; 41 | await licenseTemplate.registerLicenseTerms(testTerms).then((tx) => tx.wait()); 42 | const licenseTermsId = await licenseTemplate.getLicenseTermsId(testTerms); 43 | console.log("licenseTermsId", licenseTermsId); 44 | 45 | return licenseTermsId; 46 | } 47 | -------------------------------------------------------------------------------- /test/hardhat/e2e/utils/nftHelper.ts: -------------------------------------------------------------------------------- 1 | // Purpose: Helper function to mint a new NFT and return the tokenId 2 | 3 | import hre from "hardhat" 4 | import { MockERC721 } from "../constants"; 5 | import { ethers } from "ethers"; 6 | 7 | 8 | export async function mintNFT(singer?: ethers.Wallet, toAddress?: string, nonce?: number): Promise { 9 | let tokenId: any 10 | const contractAbi = [ 11 | { 12 | inputs: [{ internalType: "address", name: "to", type: "address" }], 13 | name: "mint", 14 | outputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], 15 | stateMutability: "nonpayable", 16 | type: "function", 17 | }, 18 | ] 19 | 20 | const caller = singer || (await hre.ethers.getSigners())[0] 21 | const nftContract = await hre.ethers.getContractAt(contractAbi, MockERC721, caller); 22 | const mintToAddress = toAddress || caller.address; 23 | 24 | const tx = await nftContract.mint(mintToAddress, { nonce: nonce }) 25 | const receipt = await tx.wait() 26 | 27 | const logs = receipt.logs 28 | 29 | if (logs[0].topics[3]) { 30 | tokenId = parseInt(logs[0].topics[3], 16) 31 | console.log(`Minted NFT tokenId: ${tokenId}`) 32 | } 33 | 34 | return tokenId 35 | } 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "incremental": true, 5 | "target": "esnext", 6 | "module": "commonjs", 7 | "outDir": "dist", 8 | "pretty": true, 9 | "removeComments": true, 10 | 11 | /* Strict Type-Checking Options */ 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "strictBindCallApply": true, 17 | "strictPropertyInitialization": true, 18 | "noImplicitThis": false, 19 | "alwaysStrict": true, 20 | 21 | /* Additional Checks */ 22 | "noUnusedLocals": false, 23 | "noUnusedParameters": false, 24 | "noImplicitReturns": false, 25 | "noFallthroughCasesInSwitch": false, 26 | "skipLibCheck": true, 27 | 28 | /* Module Resolution Options */ 29 | "moduleResolution": "node", 30 | "esModuleInterop": true, 31 | "resolveJsonModule": true, 32 | "sourceMap": true, 33 | "forceConsistentCasingInFileNames": true, 34 | "types": ["node"], 35 | "baseUrl": "src", 36 | "paths": { 37 | "@gtp/*": ["./*"] 38 | } 39 | }, 40 | "include": ["script/hardhat/**/*"], 41 | "exclude": ["node_modules"] 42 | } 43 | -------------------------------------------------------------------------------- /upgrades/v1.3.1-to-v1.3.2/chainId-1315/transactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [ 3 | { 4 | "schedule": { 5 | "transaction_hash": "0xd1609b89d3146b29dfde99cc69f6fec961a1715f380711118a7130ca96400298", 6 | "explorer_link": "https://aeneid.storyscan.io/tx/0xd1609b89d3146b29dfde99cc69f6fec961a1715f380711118a7130ca96400298" 7 | } 8 | }, 9 | { 10 | "execute": { 11 | "transaction_hash": "0x394a42e12225710fe7937cd8f7527e36af6671c5d9c39c7e90f22f4f46716b5a", 12 | "explorer_link": "https://aeneid.storyscan.io/tx/0x394a42e12225710fe7937cd8f7527e36af6671c5d9c39c7e90f22f4f46716b5a" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /upgrades/v1.3.1-to-v1.3.2/chainId-1315/upgrade-v1.3.1-to-v1.3.2-1315.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "AccessController-NewImpl": "0xC34bE4D343248dE1361Fd265Acd06a8256422d80", 4 | "AccessController-Proxy": "0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a", 5 | "ArbitrationPolicyUMA-NewImpl": "0xC9b147712BDA7a1CdF39fE3604919cFDcb0bD45E", 6 | "ArbitrationPolicyUMA-Proxy": "0xfFD98c3877B8789124f02C7E8239A4b0Ef11E936", 7 | "CoreMetadataModule-NewImpl": "0x3D305062e3A94B3b8321FEdf5C9083b52329DF1E", 8 | "CoreMetadataModule-Proxy": "0x6E81a25C99C6e8430aeC7353325EB138aFE5DC16", 9 | "DisputeModule-NewImpl": "0x5B244EdB7eB5d0495A64d8cD4AeE8c882E0b0c9C", 10 | "DisputeModule-Proxy": "0x9b7A9c70AFF961C799110954fc06F3093aeb94C5", 11 | "EvenSplitGroupPool-NewImpl": "0xEC3e3117281846F6365414225101E2E98E2Bc326", 12 | "EvenSplitGroupPool-Proxy": "0xf96f2c30b41Cb6e0290de43C8528ae83d4f33F89", 13 | "GroupNFT-NewImpl": "0xFA2595F130eb224AfAdf4c926cAEA8A651623b0B", 14 | "GroupNFT-Proxy": "0x4709798FeA84C84ae2475fF0c25344115eE1529f", 15 | "GroupingModule-NewImpl": "0x7cd634aD454a18CD4191f73b22fc64ab9a82ED7c", 16 | "GroupingModule-Proxy": "0x69D3a7aa9edb72Bc226E745A7cCdd50D947b69Ac", 17 | "IPAccountImplCode-NewImpl": "0x2EC2f6AA34194D98cc50E948D39277Fd45Fc6C9b", 18 | "IPAccountImplCode-Proxy": "0x77319B4031e6eF1250907aa00018B8B1c67a244b", 19 | "IPAssetRegistry-NewImpl": "0x055B5198CE692087644cFe33240F7e1cC17C483a", 20 | "IPAssetRegistry-Proxy": "0x77319B4031e6eF1250907aa00018B8B1c67a244b", 21 | "IpRoyaltyVault-NewImpl": "0xd0e60e2e29B8b11a0a5ff3EEA7765C09248e3Bc1", 22 | "IpRoyaltyVault-Proxy": "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086", 23 | "LicenseRegistry-NewImpl": "0xEdb8CF17593Cc5BCe078304028b1c7c4f139d13f", 24 | "LicenseRegistry-Proxy": "0x529a750E02d8E2f15649c13D69a465286a780e24", 25 | "LicenseToken-NewImpl": "0xE553aA5368982eB76B5C67766E822548D8A09982", 26 | "LicenseToken-Proxy": "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC", 27 | "LicensingModule-NewImpl": "0xBFC9C1c1cAc3b4A0c572B7De8183Cd239DCB4dE0", 28 | "LicensingModule-Proxy": "0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f", 29 | "ModuleRegistry-NewImpl": "0xAfD1a9c053dceb9e2199bfa82f494689a51e8BD0", 30 | "ModuleRegistry-Proxy": "0x022DBAAeA5D8fB31a0Ad793335e39Ced5D631fa5", 31 | "PILicenseTemplate-NewImpl": "0x0ED73a5250A9a99FA142f6627D1B7547b6fFD7aA", 32 | "PILicenseTemplate-Proxy": "0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316", 33 | "ProtocolPauseAdmin-NewImpl": "0x1Ce9348895bdE4956cdA370C82f63D44D70682A8", 34 | "ProtocolPauseAdmin-Proxy": "0xdd661f55128A80437A0c0BDA6E13F214A3B2EB24", 35 | "RoyaltyModule-NewImpl": "0x5Dac5Cf61C4a2bAAf4F5A92C3E37bBC39c2152eA", 36 | "RoyaltyModule-Proxy": "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086", 37 | "RoyaltyPolicyLAP-NewImpl": "0x09faE3C0Fb38683a44a67c98559Ef070331Eac78", 38 | "RoyaltyPolicyLAP-Proxy": "0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E", 39 | "RoyaltyPolicyLRP-NewImpl": "0x5FdB29ab51962942c67368b6D5b6C9806BaE468c", 40 | "RoyaltyPolicyLRP-Proxy": "0x9156e603C949481883B1d3355c6f1132D191fC41" 41 | } 42 | } -------------------------------------------------------------------------------- /upgrades/v1.3.1-to-v1.3.2/chainId-1514/transactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [ 3 | { 4 | "schedule": { 5 | "transaction_hash": "0xd914b5ceb54ffca0349ecdd6429a0e1338d8802958ffccff466a276bb15724c9", 6 | "explorer_link": "https://www.storyscan.io/tx/0xd914b5ceb54ffca0349ecdd6429a0e1338d8802958ffccff466a276bb15724c9" 7 | } 8 | }, 9 | { 10 | "execute": { 11 | "transaction_hash": "0x9576832f208304275d8e7d3344eb333b35d9d320af3e07ca56052a839c7dfea5", 12 | "explorer_link": "https://www.storyscan.io/tx/0x9576832f208304275d8e7d3344eb333b35d9d320af3e07ca56052a839c7dfea5" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /upgrades/v1.3.1-to-v1.3.2/chainId-1514/upgrade-v1.3.1-to-v1.3.2-1514.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "AccessController-NewImpl": "0x7D8768514D50A3745268eA11Af3990B70F462A9E", 4 | "AccessController-Proxy": "0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a", 5 | "ArbitrationPolicyUMA-NewImpl": "0x2f175eba5BfD4fd7e8Ea3d829081795eae3c0316", 6 | "ArbitrationPolicyUMA-Proxy": "0xfFD98c3877B8789124f02C7E8239A4b0Ef11E936", 7 | "CoreMetadataModule-NewImpl": "0x4a9B93EC17f0cdE1F1eaB74e931C705B2F145A2e", 8 | "CoreMetadataModule-Proxy": "0x6E81a25C99C6e8430aeC7353325EB138aFE5DC16", 9 | "DisputeModule-NewImpl": "0x0d2D4D632EfCABa429131acCDBBb9DCbB391Fe40", 10 | "DisputeModule-Proxy": "0x9b7A9c70AFF961C799110954fc06F3093aeb94C5", 11 | "EvenSplitGroupPool-NewImpl": "0xabbbAfF7916cED8ea28296A16cA71F8ae9242C70", 12 | "EvenSplitGroupPool-Proxy": "0xf96f2c30b41Cb6e0290de43C8528ae83d4f33F89", 13 | "GroupNFT-NewImpl": "0x757587bE99C94a5c00Ef28e7F4DDf2Fb9195873c", 14 | "GroupNFT-Proxy": "0x4709798FeA84C84ae2475fF0c25344115eE1529f", 15 | "GroupingModule-NewImpl": "0x898026f26bc35678f48B8271EDff692c465Ba407", 16 | "GroupingModule-Proxy": "0x69D3a7aa9edb72Bc226E745A7cCdd50D947b69Ac", 17 | "IPAccountImplCode-NewImpl": "0x065B70509F184488288cDa334828547031d010dD", 18 | "IPAccountImplCode-Proxy": "0x77319B4031e6eF1250907aa00018B8B1c67a244b", 19 | "IPAssetRegistry-NewImpl": "0xa60085269D35d93c298a9151689C962c2771f981", 20 | "IPAssetRegistry-Proxy": "0x77319B4031e6eF1250907aa00018B8B1c67a244b", 21 | "IpRoyaltyVault-NewImpl": "0x9bCc4C61C506d5Cb990f0f5a817f643de3Ee16C9", 22 | "IpRoyaltyVault-Proxy": "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086", 23 | "LicenseRegistry-NewImpl": "0x96fd34e876D84e71e4FC5DACd7A6661B2aA68ae1", 24 | "LicenseRegistry-Proxy": "0x529a750E02d8E2f15649c13D69a465286a780e24", 25 | "LicenseToken-NewImpl": "0xc010220b0e046D1E0b35b76554d43BBCaE1808e7", 26 | "LicenseToken-Proxy": "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC", 27 | "LicensingModule-NewImpl": "0x28522C65aE32989517FB890dB679bd3BBDc35a6B", 28 | "LicensingModule-Proxy": "0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f", 29 | "ModuleRegistry-NewImpl": "0x5C5df3374c1F3B908Dd2f5D8f15135f3D728Dbcd", 30 | "ModuleRegistry-Proxy": "0x022DBAAeA5D8fB31a0Ad793335e39Ced5D631fa5", 31 | "PILicenseTemplate-NewImpl": "0xeEac3442590EC55367B82cb1A57B95Ae824f534e", 32 | "PILicenseTemplate-Proxy": "0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316", 33 | "ProtocolPauseAdmin-NewImpl": "0xF110240F7fd56D8201d4643e71640c09aCa10a92", 34 | "ProtocolPauseAdmin-Proxy": "0xdd661f55128A80437A0c0BDA6E13F214A3B2EB24", 35 | "RoyaltyModule-NewImpl": "0x0ce7422690a173e70b38240122F185997529E162", 36 | "RoyaltyModule-Proxy": "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086", 37 | "RoyaltyPolicyLAP-NewImpl": "0x0Dbe3a825f6E557D2f02f60A9fC586584ed5068e", 38 | "RoyaltyPolicyLAP-Proxy": "0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E", 39 | "RoyaltyPolicyLRP-NewImpl": "0xCB5BD63AF7c6Da65A6b21b56Da197F675a4e3382", 40 | "RoyaltyPolicyLRP-Proxy": "0x9156e603C949481883B1d3355c6f1132D191fC41" 41 | } 42 | } --------------------------------------------------------------------------------