├── .example.env ├── .github └── workflows │ ├── publish.yml │ ├── system-test.yml │ └── unit-test.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── .vscode └── settings.json ├── README.md ├── contracts ├── JBChainlinkV3PriceFeed.sol ├── JBController.sol ├── JBDirectory.sol ├── JBERC20PaymentTerminal.sol ├── JBETHERC20ProjectPayer.sol ├── JBETHERC20ProjectPayerDeployer.sol ├── JBETHERC20SplitsPayer.sol ├── JBETHERC20SplitsPayerDeployer.sol ├── JBETHPaymentTerminal.sol ├── JBFundingCycleStore.sol ├── JBOperatorStore.sol ├── JBPrices.sol ├── JBProjects.sol ├── JBReconfigurationBufferBallot.sol ├── JBSingleTokenPaymentTerminalStore.sol ├── JBSplitsStore.sol ├── JBToken.sol ├── JBTokenStore.sol ├── abstract │ ├── JBControllerUtility.sol │ ├── JBOperatable.sol │ ├── JBPayoutRedemptionPaymentTerminal.sol │ └── JBSingleTokenPaymentTerminal.sol ├── enums │ └── JBBallotState.sol ├── interfaces │ ├── IJBAllowanceTerminal.sol │ ├── IJBController.sol │ ├── IJBControllerUtility.sol │ ├── IJBDirectory.sol │ ├── IJBETHERC20ProjectPayerDeployer.sol │ ├── IJBETHERC20SplitsPayerDeployer.sol │ ├── IJBFeeGauge.sol │ ├── IJBFundingCycleBallot.sol │ ├── IJBFundingCycleDataSource.sol │ ├── IJBFundingCycleStore.sol │ ├── IJBMigratable.sol │ ├── IJBOperatable.sol │ ├── IJBOperatorStore.sol │ ├── IJBPayDelegate.sol │ ├── IJBPaymentTerminal.sol │ ├── IJBPayoutRedemptionPaymentTerminal.sol │ ├── IJBPayoutTerminal.sol │ ├── IJBPriceFeed.sol │ ├── IJBPrices.sol │ ├── IJBProjectPayer.sol │ ├── IJBProjects.sol │ ├── IJBReconfigurationBufferBallot.sol │ ├── IJBRedemptionDelegate.sol │ ├── IJBRedemptionTerminal.sol │ ├── IJBSingleTokenPaymentTerminal.sol │ ├── IJBSingleTokenPaymentTerminalStore.sol │ ├── IJBSplitAllocator.sol │ ├── IJBSplitsPayer.sol │ ├── IJBSplitsStore.sol │ ├── IJBTerminalUtility.sol │ ├── IJBToken.sol │ ├── IJBTokenStore.sol │ └── IJBTokenUriResolver.sol ├── libraries │ ├── JBConstants.sol │ ├── JBCurrencies.sol │ ├── JBFixedPointNumber.sol │ ├── JBFundingCycleMetadataResolver.sol │ ├── JBGlobalFundingCycleMetadataResolver.sol │ ├── JBOperations.sol │ ├── JBSplitsGroups.sol │ └── JBTokens.sol ├── structs │ ├── JBDidPayData.sol │ ├── JBDidRedeemData.sol │ ├── JBFee.sol │ ├── JBFundAccessConstraints.sol │ ├── JBFundingCycle.sol │ ├── JBFundingCycleData.sol │ ├── JBFundingCycleMetadata.sol │ ├── JBGlobalFundingCycleMetadata.sol │ ├── JBGroupedSplits.sol │ ├── JBOperatorData.sol │ ├── JBPayParamsData.sol │ ├── JBProjectMetadata.sol │ ├── JBRedeemParamsData.sol │ ├── JBSplit.sol │ ├── JBSplitAllocationData.sol │ └── JBTokenAmount.sol └── system_tests │ ├── TestAllowance.sol │ ├── TestDistributeHeldFee.sol │ ├── TestEIP165.sol │ ├── TestERC20Terminal.sol │ ├── TestLaunchProject.sol │ ├── TestMultipleTerminals.sol │ ├── TestPayBurnRedeemFlow.sol │ ├── TestReconfigure.sol │ ├── TestTokenFlow.sol │ ├── helpers │ ├── AccessJBLib.sol │ ├── TestBaseWorkflow.sol │ └── hevm.sol │ └── mock │ └── MockPriceFeed.sol ├── deploy ├── 1.js ├── 2.js └── 3.js ├── deployments ├── goerli │ ├── .chainId │ ├── JBChainlinkV3PriceFeed.json │ ├── JBController.json │ ├── JBCurrencies.json │ ├── JBDirectory.json │ ├── JBETHERC20ProjectPayerDeployer.json │ ├── JBETHERC20SplitsPayerDeployer.json │ ├── JBETHPaymentTerminal.json │ ├── JBFundingCycleStore.json │ ├── JBOperatorStore.json │ ├── JBPrices.json │ ├── JBProjects.json │ ├── JBReconfigurationBufferBallot.json │ ├── JBSingleTokenPaymentTerminalStore.json │ ├── JBSplitsStore.json │ ├── JBTokenStore.json │ └── solcInputs │ │ ├── c02563cf32cc775f61feb8c17fb4ee90.json │ │ └── e9bf28e6b06ac987169c3c7105c7ab19.json ├── mainnet │ ├── .chainId │ ├── JB3DayReconfigurationBufferBallot.json │ ├── JB7DayReconfigurationBufferBallot.json │ ├── JBChainlinkV3PriceFeed.json │ ├── JBController.json │ ├── JBCurrencies.json │ ├── JBDirectory.json │ ├── JBETHERC20ProjectPayerDeployer.json │ ├── JBETHERC20SplitsPayerDeployer.json │ ├── JBETHPaymentTerminal.json │ ├── JBFundingCycleStore.json │ ├── JBOperatorStore.json │ ├── JBPrices.json │ ├── JBProjects.json │ ├── JBSingleTokenPaymentTerminalStore.json │ ├── JBSplitsStore.json │ ├── JBTokenStore.json │ └── solcInputs │ │ ├── 153d6bc38185326110a6246705507380.json │ │ ├── 36426b5abae189028f71abc80f5dc8b7.json │ │ ├── 4138c0c854528929e18f6ccc34d600b0.json │ │ ├── 87acdcf5deeaa43ae3ecf62f45455645.json │ │ ├── a1f674e02c4866a16e5bde886a31b82e.json │ │ └── ba079c3b841cf131865f97c4e38baa17.json └── rinkeby │ ├── .chainId │ ├── JB3DayReconfigurationBufferBallot.json │ ├── JB7DayReconfigurationBufferBallot.json │ ├── JBChainlinkV3PriceFeed.json │ ├── JBController.json │ ├── JBCurrencies.json │ ├── JBDirectory.json │ ├── JBETHERC20ProjectPayerDeployer.json │ ├── JBETHERC20SplitsPayerDeployer.json │ ├── JBETHPaymentTerminal.json │ ├── JBFundingCycleStore.json │ ├── JBOperatorStore.json │ ├── JBPrices.json │ ├── JBProjects.json │ ├── JBSingleTokenPaymentTerminalStore.json │ ├── JBSplitsStore.json │ ├── JBTokenStore.json │ └── solcInputs │ ├── 142966ac46606d4b06fc726df52b60c2.json │ ├── 153d6bc38185326110a6246705507380.json │ ├── 36426b5abae189028f71abc80f5dc8b7.json │ ├── 4138c0c854528929e18f6ccc34d600b0.json │ ├── 87acdcf5deeaa43ae3ecf62f45455645.json │ ├── a1f674e02c4866a16e5bde886a31b82e.json │ └── ba079c3b841cf131865f97c4e38baa17.json ├── foundry.toml ├── hardhat.config.js ├── lib └── ds-test │ ├── LICENSE │ ├── Makefile │ ├── default.nix │ ├── demo │ └── demo.sol │ └── src │ └── test.sol ├── package.json ├── remappings.txt ├── security └── postmortem │ └── 5.24.2022.md ├── test ├── helpers │ ├── errors.json │ └── utils.js ├── jb_chainlink_price_feed │ └── current_price.test.js ├── jb_controller │ ├── burn_tokens_of.test.js │ ├── change_token_of.test.js │ ├── distribute_reserved_token_of.test.js │ ├── issue_token_for.test.js │ ├── launch_funding_cycle_for.test.js │ ├── launch_project_for.test.js │ ├── migrate.test.js │ ├── mint_tokens_of.test.js │ ├── prep_for_migration.test.js │ ├── reconfigure_funding_cycles_of.test.js │ └── total_oustanding_tokens_of.test.js ├── jb_directory │ ├── is_terminal_of.test.js │ ├── primary_terminal_of.test.js │ ├── set_controller_of.test.js │ ├── set_is_allowed_to_set_first_controller.test.js │ ├── set_primary_terminal_of.test.js │ ├── set_terminals_of.test.js │ └── terminals_of.test.js ├── jb_eth_erc20_project_payer │ ├── add_to_balance.test.js │ ├── pay.test.js │ └── set_default_values.test.js ├── jb_eth_erc20_splits_payer │ ├── add_to_balance.test.js │ ├── pay.test.js │ ├── receive.test.js │ └── set_default_splits.test.js ├── jb_eth_erc20_splits_payer_deployer │ └── deploy_split_payer.test.js ├── jb_funding_cycle_store │ └── configure_for.test.js ├── jb_operator_store │ ├── has_permission.test.js │ ├── has_permissions.test.js │ ├── set_operator.test.js │ └── set_operators.test.js ├── jb_payment_terminal │ ├── 1 │ │ ├── add_to_balance_of.test.js │ │ ├── current_eth_overflow_of.test.js │ │ ├── distribute_payouts_of.test.js │ │ ├── migrate.test.js │ │ ├── pay.test.js │ │ ├── redeem_tokens_of.test.js │ │ ├── set_fee.test.js │ │ ├── set_fee_gauge.test.js │ │ ├── set_feeless_terminal.test.js │ │ ├── use_allowance_of.test.js │ │ └── view.test.js │ └── 2 │ │ ├── add_to_balance_of.test.js │ │ ├── current_eth_overflow_of.test.js │ │ ├── distribute_payouts_of.test.js │ │ ├── migrate.test.js │ │ ├── pay.test.js │ │ ├── redeem_tokens_of.test.js │ │ ├── set_fee.test.js │ │ ├── set_fee_gauge.test.js │ │ ├── set_feeless_terminal.test.js │ │ ├── use_allowance_of.test.js │ │ └── view.test.js ├── jb_payment_terminal_store │ ├── current_overflow_of.test.js │ ├── current_reclaimable_overflow_of.test.js │ ├── current_total_overflow_of.test.js │ ├── record_distribution_for.test.js │ ├── record_migration.test.js │ ├── record_payment_from.test.js │ ├── record_redemption_for.test.js │ └── record_used_allowance_of.test.js ├── jb_prices │ ├── add_feed_for.test.js │ └── price_for.test.js ├── jb_project_payer_deployer │ └── deploy_project_payer.test.js ├── jb_projects │ ├── create_for.test.js │ ├── set_metadata_of.test.js │ ├── set_token_uri_resolver.test.js │ └── token_uri.test.js ├── jb_reconfiguration_buffer_ballot │ └── finalize.test.js ├── jb_splits_store │ └── set.test.js ├── jb_token │ ├── approve.test.js │ ├── burn.test.js │ ├── decimals.test.js │ ├── mint.test.js │ ├── transfer.test.js │ ├── transfer_from.test.js │ └── transfer_ownership.test.js └── jb_token_store │ ├── balance_of.test.js │ ├── burn_from.test.js │ ├── change_for.test.js │ ├── claim_for.test.js │ ├── issue_for.test.js │ ├── mint_for.test.js │ ├── should_require_claiming_for.test.js │ ├── total_supply_of.test.js │ └── transfer_from.test.js └── yarn.lock /.example.env: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY= 2 | INFURA_ID= -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: juice-contracts-publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: 14 14 | - name: Get yarn cache directory path 15 | id: yarn-cache-dir-path 16 | run: echo "::set-output name=dir::$(yarn cache dir)" 17 | - name: Restore cached yarn cache 18 | uses: actions/cache@v2 19 | id: cache-yarn-cache 20 | with: 21 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 22 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 23 | restore-keys: | 24 | ${{ runner.os }}-yarn- 25 | - name: Restore cached node_modules 26 | id: cache-node-modules 27 | uses: actions/cache@v2 28 | with: 29 | path: | 30 | ./node_modules 31 | ./packages/hardhat/node_modules 32 | key: ${{ runner.os }}-${{ steps.nvm.outputs.NVMRC }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 33 | restore-keys: | 34 | ${{ runner.os }}-${{ steps.nvm.outputs.NVMRC }}-nodemodules- 35 | - name: Install JS dependencies 36 | if: | 37 | steps.cache-yarn-cache.outputs.cache-hit != 'true' || 38 | steps.cache-node-modules.outputs.cache-hit != 'true' 39 | run: yarn install --frozen-lockfile --prefer-offline 40 | # Publish deploy artifacts NPM packages. This will only run if there are new deploys. 41 | - name: Publish NPM package 42 | uses: JS-DevTools/npm-publish@v1 43 | with: 44 | token: ${{ secrets.NPM_TOKEN }} 45 | access: "public" 46 | -------------------------------------------------------------------------------- /.github/workflows/system-test.yml: -------------------------------------------------------------------------------- 1 | name: juice-contracts-system-tests 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | - test/system-tests 7 | push: 8 | branches: 9 | - main 10 | - test/system-tests 11 | jobs: 12 | forge-test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | submodules: recursive 18 | - uses: actions/setup-node@v1 19 | with: 20 | node-version: 14 21 | - name: Get yarn cache directory path 22 | id: yarn-cache-dir-path 23 | run: echo "::set-output name=dir::$(yarn cache dir)" 24 | - name: Restore cached yarn cache 25 | uses: actions/cache@v2 26 | id: cache-yarn-cache 27 | with: 28 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 29 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 30 | restore-keys: | 31 | ${{ runner.os }}-yarn- 32 | - name: Restore cached node_modules 33 | id: cache-node-modules 34 | uses: actions/cache@v2 35 | with: 36 | path: | 37 | ./node_modules 38 | ./packages/hardhat/node_modules 39 | key: ${{ runner.os }}-${{ steps.nvm.outputs.NVMRC }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 40 | restore-keys: | 41 | ${{ runner.os }}-${{ steps.nvm.outputs.NVMRC }}-nodemodules- 42 | - name: Install Foundry 43 | uses: onbjerg/foundry-toolchain@v1 44 | with: 45 | version: nightly 46 | - name: Install JS dependencies 47 | if: | 48 | steps.cache-yarn-cache.outputs.cache-hit != 'true' || 49 | steps.cache-node-modules.outputs.cache-hit != 'true' 50 | run: yarn install --frozen-lockfile --prefer-offline 51 | - name: install libraries 52 | run: git submodule update --init 53 | - name: Run system tests 54 | run: forge test --force --optimize -vvv 55 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: juice-contracts-tests 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | push: 7 | branches: 8 | - main 9 | jobs: 10 | unit-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 14 17 | - name: Get yarn cache directory path 18 | id: yarn-cache-dir-path 19 | run: echo "::set-output name=dir::$(yarn cache dir)" 20 | - name: Restore cached yarn cache 21 | uses: actions/cache@v2 22 | id: cache-yarn-cache 23 | with: 24 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 25 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 26 | restore-keys: | 27 | ${{ runner.os }}-yarn- 28 | - name: Restore cached node_modules 29 | id: cache-node-modules 30 | uses: actions/cache@v2 31 | with: 32 | path: | 33 | ./node_modules 34 | ./packages/hardhat/node_modules 35 | key: ${{ runner.os }}-${{ steps.nvm.outputs.NVMRC }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 36 | restore-keys: | 37 | ${{ runner.os }}-${{ steps.nvm.outputs.NVMRC }}-nodemodules- 38 | - name: Install JS dependencies 39 | if: | 40 | steps.cache-yarn-cache.outputs.cache-hit != 'true' || 41 | steps.cache-node-modules.outputs.cache-hit != 'true' 42 | run: yarn install --frozen-lockfile --prefer-offline 43 | - name: Run unit tests 44 | run: node --max-old-space-size=4096 --require esm ./node_modules/.bin/hardhat test --network hardhat 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | *.txt 4 | !remappings.txt 5 | 6 | # Foundry 7 | out 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | deployments/localhost/** 13 | 14 | # Mac OSX 15 | .DS_Store 16 | 17 | # Code coverage reports 18 | coverage/ 19 | coverage.json 20 | 21 | # VSCode 22 | workspace.code-workspace 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/chainlink"] 2 | path = lib/chainlink 3 | url = https://github.com/smartcontractkit/chainlink 4 | [submodule "lib/prb-math"] 5 | path = lib/prb-math 6 | url = https://github.com/hifi-finance/prb-math 7 | [submodule "lib/openzeppelin-contracts"] 8 | path = lib/openzeppelin-contracts 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything by default. 2 | * 3 | 4 | # Allowlisted deployment networks. 5 | !/deployments/rinkeby/** 6 | !/deployments/mainnet/** 7 | 8 | # Allow everything in /contracts ... 9 | !/contracts/** 10 | # ... except tests 11 | /contracts/test/** 12 | /contracts/system_tests/** 13 | /foundry.toml -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "overrides": [ 9 | { 10 | "files": "*.sol", 11 | "options": { 12 | "printWidth": 100, 13 | "tabWidth": 2, 14 | "useTabs": false, 15 | "bracketSpacing": false, 16 | "explicitTypes": "always" 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | // solidity-coverage configuration file. 2 | // 3 | // https://www.npmjs.com/package/solidity-coverage 4 | 5 | module.exports = { 6 | skipFiles: [ 7 | 'system_tests/helpers/AccessJBLib.sol', 8 | 'system_tests/helpers/hevm.sol', 9 | 'system_tests/helpers/TestBaseWorkflow.sol', 10 | 'system_tests/mock/MockPriceFeed.sol', 11 | 'system_tests/TestAllowance.sol', 12 | 'system_tests/TestERC20Terminal.sol', 13 | 'system_tests/TestLaunchProject.sol', 14 | 'system_tests/TestMultipleTerminals.sol', 15 | 'system_tests/TestPayBurnRedeemFlow.sol', 16 | 'system_tests/TestTokenFlow.sol', 17 | 'libraries/JBConstants.sol', 18 | 'libraries/JBCurrencies.sol', 19 | 'libraries/JBFixedPointNumber.sol', 20 | 'libraries/JBFundingCycleMetadataResolver.sol', 21 | 'libraries/JBOperations.sol', 22 | 'libraries/JBSplitsGroups.sol', 23 | 'libraries/JBTokens.sol', 24 | 'structs/JBDidPayData.sol', 25 | 'structs/JBDidRedeemData.sol', 26 | 'structs/JBFee.sol', 27 | 'structs/JBFundAccessConstraints.sol', 28 | 'structs/JBFundingCycle.sol', 29 | 'structs/JBFundingCycleData.sol', 30 | 'structs/JBFundingCycleMetadata.sol', 31 | 'structs/JBGroupedSplits.sol', 32 | 'structs/JBOperatorData.sol', 33 | 'structs/JBPayParamsData.sol', 34 | 'structs/JBProjectMetadata.sol', 35 | 'structs/JBRedeemParamsData.sol', 36 | 'structs/JBSplit.sol', 37 | 'structs/JBSplitAllocationData.sol', 38 | 'structs/JBTokenAmount.sol', 39 | 'interfaces/IJBController.sol', 40 | 'interfaces/IJBControllerUtility.sol', 41 | 'interfaces/IJBDirectory.sol', 42 | 'interfaces/IJBETHERC20ProjectPayerDeployer.sol', 43 | 'interfaces/IJBFeeGauge.sol', 44 | 'interfaces/IJBFundingCycleBallot.sol', 45 | 'interfaces/IJBFundingCycleDataSource.sol', 46 | 'interfaces/IJBFundingCycleStore.sol', 47 | 'interfaces/IJBOperatable.sol', 48 | 'interfaces/IJBOperatorStore.sol', 49 | 'interfaces/IJBPayDelegate.sol', 50 | 'interfaces/IJBPaymentTerminal.sol', 51 | 'interfaces/IJBPaymentTerminalStore.sol', 52 | 'interfaces/IJBPayoutRedemptionPaymentTerminal.sol', 53 | 'interfaces/IJBPriceFeed.sol', 54 | 'interfaces/IJBPrices.sol', 55 | 'interfaces/IJBProjectPayer.sol', 56 | 'interfaces/IJBProjects.sol', 57 | 'interfaces/IJBRedemptionDelegate.sol', 58 | 'interfaces/IJBSplitAllocator.sol', 59 | 'interfaces/IJBSplitsStore.sol', 60 | 'interfaces/IJBTerminalUtility.sol', 61 | 'interfaces/IJBToken.sol', 62 | 'interfaces/IJBTokenStore.sol', 63 | 'interfaces/IJBTokenUriResolver.sol', 64 | ], 65 | configureYulOptimizer: true, 66 | measureStatementCoverage: false, 67 | }; 68 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "compiler-version": ["off"], 7 | "constructor-syntax": "warn", 8 | "quotes": ["error", "single"], 9 | "func-visibility": ["warn", { "ignoreConstructors": true }], 10 | "not-rely-on-time": "off", 11 | "private-vars-leading-underscore": ["warn", { "strict": false }], 12 | "const-name-snakecase": "warn", 13 | "comprehensive-interface": "error", 14 | "func-param-name-mixedcase": "error", 15 | "modifier-name-mixedcase": "error" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.compileUsingRemoteVersion": "0.8.6", 3 | "editor.formatOnSave": true, 4 | "[javascript]": { 5 | "editor.tabSize": 2 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # juice-contracts-v2 2 | 3 | ## Develop 4 | 5 | ### Unit Tests 6 | 7 | To run the unit tests suite (in Javascript), you'll need to run `yarn install` first then manually run Hardhat in order to enable ESM support: 8 | 9 | ```bash 10 | node --require esm ./node_modules/.bin/hardhat test --network hardhat 11 | ``` 12 | 13 | Alternatively, you can run a local Hardhat node in another terminal using 14 | 15 | ```bash 16 | yarn chain --network hardhat 17 | ``` 18 | 19 | then run the following: 20 | 21 | ```bash 22 | yarn test 23 | ``` 24 | 25 | It might happens that Hardhat cannot resolve custom error (test failing on "Expecter nameOfTheError() but reverted 26 | without a reason string"), just restart yarn chain. 27 | 28 | ### System Tests 29 | 30 | End-to-end tests have been written in Solidity, using Foundry. 31 | 32 | To get set up: 33 | 34 | 1. Install [Foundry](https://github.com/gakonst/foundry). 35 | 36 | ```bash 37 | curl -L https://foundry.paradigm.xyz | sh 38 | ``` 39 | 40 | 2. Install external lib(s) 41 | 42 | ```bash 43 | git submodule update --init 44 | ``` 45 | 46 | 3. Run tests: 47 | 48 | ```bash 49 | forge test 50 | ``` 51 | 52 | 4. Update Foundry periodically: 53 | 54 | ```bash 55 | foundryup 56 | ``` 57 | 58 | Resources: 59 | 60 | - [The Forge-Book](https://onbjerg.github.io/foundry-book/forge) 61 | 62 | ### Coverage 63 | 64 | To check current unit tests coverage: 65 | 66 | ```bash 67 | node --require esm ./node_modules/.bin/hardhat coverage --network hardhat 68 | ``` 69 | 70 | A few notes: 71 | 72 | - Hardhat doesn't support [esm](https://nodejs.org/api/esm.html) yet, hence running manually with node. 73 | - We are currently using a forked version of [solidity-coverage](https://www.npmjs.com/package/solidity-coverage) that includes optimizer settings. Ideally we will move to the maintained version after this is fixed on their end. 74 | - Juicebox V2 codebase being quite large, Solidity Coverage might run out of memory if you modify/add parts to it. Please check [Solidity-coverage FAQ](https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md) in order to address the issue. 75 | 76 | ## Deploy 77 | 78 | Juicebox uses the [Hardhat Deploy](https://github.com/wighawag/hardhat-deploy) plugin to deploy contracts to a given network. But before using it, you must create a `./mnemonic.txt` file containing the mnemonic phrase of the wallet used to deploy. You can generate a new mnemonic using [this tool](https://github.com/itinance/mnemonics). Generate a mnemonic at your own risk. 79 | 80 | Then, to execute the `./deploy/deploy.js` script, run the following: 81 | 82 | ```bash 83 | npx hardhat deploy --network $network 84 | ``` 85 | 86 | \_You'll likely want to set the optimizer runs to 10000 in `./hardhat.config.js` before deploying to prevent contract size errors. The preset value of 1000000 is necessary for hardhat to run unit tests successfully. Bug about this opened [here](https://github.com/NomicFoundation/hardhat/issues/2657#issuecomment-1113890401). 87 | 88 | Contract artifacts will be outputted to `./deployments/$network/**` and should be checked in to the repo. 89 | 90 | ## Verification 91 | 92 | To verify the contracts on [Etherscan](https://etherscan.io), make sure you have an `ETHERSCAN_API_KEY` set in your `./.env` file. Then run the following: 93 | 94 | ```bash 95 | npx hardhat --network $network etherscan-verify 96 | ``` 97 | 98 | This will verify all of the deployed contracts in `./deployments`. 99 | -------------------------------------------------------------------------------- /contracts/JBChainlinkV3PriceFeed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import '@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol'; 5 | import './interfaces/IJBPriceFeed.sol'; 6 | import './libraries/JBFixedPointNumber.sol'; 7 | 8 | /** 9 | @notice 10 | A generalized price feed for the Chainlink AggregatorV3Interface. 11 | 12 | @dev 13 | Adheres to - 14 | IJBPriceFeed: General interface for the methods in this contract that interact with the blockchain's state according to the protocol's rules. 15 | */ 16 | contract JBChainlinkV3PriceFeed is IJBPriceFeed { 17 | // A library that provides utility for fixed point numbers. 18 | using JBFixedPointNumber for uint256; 19 | 20 | //*********************************************************************// 21 | // --------------------- public stored properties -------------------- // 22 | //*********************************************************************// 23 | 24 | /** 25 | @notice 26 | The feed that prices are reported from. 27 | */ 28 | AggregatorV3Interface public feed; 29 | 30 | //*********************************************************************// 31 | // ------------------------- external views -------------------------- // 32 | //*********************************************************************// 33 | 34 | /** 35 | @notice 36 | Gets the current price from the feed, normalized to the specified number of decimals. 37 | 38 | @param _decimals The number of decimals the returned fixed point price should include. 39 | 40 | @return The current price of the feed, as a fixed point number with the specified number of decimals. 41 | */ 42 | function currentPrice(uint256 _decimals) external view override returns (uint256) { 43 | // Get the latest round information. Only need the price is needed. 44 | (, int256 _price, , , ) = feed.latestRoundData(); 45 | 46 | // Get a reference to the number of decimals the feed uses. 47 | uint256 _feedDecimals = feed.decimals(); 48 | 49 | // Return the price, adjusted to the target decimals. 50 | return uint256(_price).adjustDecimals(_feedDecimals, _decimals); 51 | } 52 | 53 | //*********************************************************************// 54 | // -------------------------- constructor ---------------------------- // 55 | //*********************************************************************// 56 | 57 | /** 58 | @param _feed The feed to report prices from. 59 | */ 60 | constructor(AggregatorV3Interface _feed) { 61 | feed = _feed; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/JBERC20PaymentTerminal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol'; 5 | import './abstract/JBPayoutRedemptionPaymentTerminal.sol'; 6 | 7 | /** 8 | @notice 9 | Manages the inflows and outflows of an ERC-20 token. 10 | 11 | @dev 12 | Adheres to - 13 | IJBProjectPayer: General interface for the methods in this contract that interact with the blockchain's state according to the protocol's rules. 14 | 15 | @dev 16 | Inherits from - 17 | JBPayoutRedemptionPaymentTerminal: Includes convenience functionality for checking a message sender's permissions before executing certain transactions. 18 | */ 19 | contract JBERC20PaymentTerminal is JBPayoutRedemptionPaymentTerminal { 20 | //*********************************************************************// 21 | // -------------------------- constructor ---------------------------- // 22 | //*********************************************************************// 23 | 24 | /** 25 | @param _token The token that this terminal manages. 26 | @param _currency The currency that this terminal's token adheres to for price feeds. 27 | @param _baseWeightCurrency The currency to base token issuance on. 28 | @param _payoutSplitsGroup The group that denotes payout splits from this terminal in the splits store. 29 | @param _operatorStore A contract storing operator assignments. 30 | @param _projects A contract which mints ERC-721's that represent project ownership and transfers. 31 | @param _directory A contract storing directories of terminals and controllers for each project. 32 | @param _splitsStore A contract that stores splits for each project. 33 | @param _prices A contract that exposes price feeds. 34 | @param _store A contract that stores the terminal's data. 35 | @param _owner The address that will own this contract. 36 | */ 37 | constructor( 38 | IERC20Metadata _token, 39 | uint256 _currency, 40 | uint256 _baseWeightCurrency, 41 | uint256 _payoutSplitsGroup, 42 | IJBOperatorStore _operatorStore, 43 | IJBProjects _projects, 44 | IJBDirectory _directory, 45 | IJBSplitsStore _splitsStore, 46 | IJBPrices _prices, 47 | IJBSingleTokenPaymentTerminalStore _store, 48 | address _owner 49 | ) 50 | JBPayoutRedemptionPaymentTerminal( 51 | address(_token), 52 | _token.decimals(), 53 | _currency, 54 | _baseWeightCurrency, 55 | _payoutSplitsGroup, 56 | _operatorStore, 57 | _projects, 58 | _directory, 59 | _splitsStore, 60 | _prices, 61 | _store, 62 | _owner 63 | ) 64 | // solhint-disable-next-line no-empty-blocks 65 | { 66 | 67 | } 68 | 69 | //*********************************************************************// 70 | // ---------------------- internal transactions ---------------------- // 71 | //*********************************************************************// 72 | 73 | /** 74 | @notice 75 | Transfers tokens. 76 | 77 | @param _from The address from which the transfer should originate. 78 | @param _to The address to which the transfer should go. 79 | @param _amount The amount of the transfer, as a fixed point number with the same number of decimals as this terminal. 80 | */ 81 | function _transferFrom( 82 | address _from, 83 | address payable _to, 84 | uint256 _amount 85 | ) internal override { 86 | _from == address(this) 87 | ? IERC20(token).transfer(_to, _amount) 88 | : IERC20(token).transferFrom(_from, _to, _amount); 89 | } 90 | 91 | /** 92 | @notice 93 | Logic to be triggered before transferring tokens from this terminal. 94 | 95 | @param _to The address to which the transfer is going. 96 | @param _amount The amount of the transfer, as a fixed point number with the same number of decimals as this terminal. 97 | */ 98 | function _beforeTransferTo(address _to, uint256 _amount) internal override { 99 | IERC20(token).approve(_to, _amount); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /contracts/JBETHERC20ProjectPayerDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import './interfaces/IJBETHERC20ProjectPayerDeployer.sol'; 5 | import './JBETHERC20ProjectPayer.sol'; 6 | 7 | /** 8 | @notice 9 | Deploys project payer contracts. 10 | 11 | @dev 12 | Adheres to - 13 | IJBETHERC20ProjectPayerDeployer: General interface for the methods in this contract that interact with the blockchain's state according to the protocol's rules. 14 | */ 15 | contract JBETHERC20ProjectPayerDeployer is IJBETHERC20ProjectPayerDeployer { 16 | //*********************************************************************// 17 | // ---------------------- external transactions ---------------------- // 18 | //*********************************************************************// 19 | 20 | /** 21 | @notice 22 | Allows anyone to deploy a new project payer contract. 23 | 24 | @param _defaultProjectId The ID of the project whose treasury should be forwarded the project payer contract's received payments. 25 | @param _defaultBeneficiary The address that'll receive the project's tokens when the project payer receives payments. 26 | @param _defaultPreferClaimedTokens A flag indicating whether issued tokens from the project payer's received payments should be automatically claimed into the beneficiary's wallet. 27 | @param _defaultMemo The memo that'll be forwarded with the project payer's received payments. 28 | @param _defaultMetadata The metadata that'll be forwarded with the project payer's received payments. 29 | @param _defaultPreferAddToBalance A flag indicating if received payments should call the `pay` function or the `addToBalance` function of a project. 30 | @param _directory A contract storing directories of terminals and controllers for each project. 31 | @param _owner The address that will own the project payer. 32 | 33 | @return projectPayer The project payer contract. 34 | */ 35 | function deployProjectPayer( 36 | uint256 _defaultProjectId, 37 | address payable _defaultBeneficiary, 38 | bool _defaultPreferClaimedTokens, 39 | string memory _defaultMemo, 40 | bytes memory _defaultMetadata, 41 | bool _defaultPreferAddToBalance, 42 | IJBDirectory _directory, 43 | address _owner 44 | ) external override returns (IJBProjectPayer projectPayer) { 45 | // Deploy the project payer. 46 | projectPayer = new JBETHERC20ProjectPayer( 47 | _defaultProjectId, 48 | _defaultBeneficiary, 49 | _defaultPreferClaimedTokens, 50 | _defaultMemo, 51 | _defaultMetadata, 52 | _defaultPreferAddToBalance, 53 | _directory, 54 | _owner 55 | ); 56 | 57 | emit DeployProjectPayer( 58 | projectPayer, 59 | _defaultProjectId, 60 | _defaultBeneficiary, 61 | _defaultPreferClaimedTokens, 62 | _defaultMemo, 63 | _defaultMetadata, 64 | _defaultPreferAddToBalance, 65 | _directory, 66 | _owner, 67 | msg.sender 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/JBETHERC20SplitsPayerDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import './interfaces/IJBETHERC20SplitsPayerDeployer.sol'; 5 | import './JBETHERC20SplitsPayer.sol'; 6 | 7 | /** 8 | @notice 9 | Deploys splits payer contracts. 10 | 11 | @dev 12 | Adheres to - 13 | IJBETHERC20SplitsPayerDeployer: General interface for the methods in this contract that interact with the blockchain's state according to the protocol's rules. 14 | */ 15 | contract JBETHERC20SplitsPayerDeployer is IJBETHERC20SplitsPayerDeployer { 16 | //*********************************************************************// 17 | // ---------------------- external transactions ---------------------- // 18 | //*********************************************************************// 19 | 20 | /** 21 | @notice 22 | Allows anyone to deploy a new splits payer contract. 23 | 24 | @param _defaultSplitsProjectId The ID of project for which the default splits are stored. 25 | @param _defaultSplitsDomain The splits domain to payout when this contract receives direct payments. 26 | @param _defaultSplitsGroup The splits group to payout when this contract receives direct payments. 27 | @param _splitsStore A contract that stores splits for each project. 28 | @param _defaultProjectId The ID of the project whose treasury should be forwarded the splits payer contract's received payment leftovers after distributing to the default splits group. 29 | @param _defaultBeneficiary The address that'll receive the project's tokens when the splits payer receives payments. 30 | @param _defaultPreferClaimedTokens A flag indicating whether issued tokens from the splits payer's received payments should be automatically claimed into the beneficiary's wallet. 31 | @param _defaultMemo The memo that'll be forwarded with the splits payer's received payments. 32 | @param _defaultMetadata The metadata that'll be forwarded with the splits payer's received payments. 33 | @param _defaultPreferAddToBalance A flag indicating if received payments should call the `pay` function or the `addToBalance` function of a project. 34 | @param _owner The address that will own the splits payer. 35 | 36 | @return splitsPayer The splits payer contract. 37 | */ 38 | function deploySplitsPayer( 39 | uint256 _defaultSplitsProjectId, 40 | uint256 _defaultSplitsDomain, 41 | uint256 _defaultSplitsGroup, 42 | IJBSplitsStore _splitsStore, 43 | uint256 _defaultProjectId, 44 | address payable _defaultBeneficiary, 45 | bool _defaultPreferClaimedTokens, 46 | string memory _defaultMemo, 47 | bytes memory _defaultMetadata, 48 | bool _defaultPreferAddToBalance, 49 | address _owner 50 | ) external override returns (IJBSplitsPayer splitsPayer) { 51 | // Deploy the splits payer. 52 | splitsPayer = new JBETHERC20SplitsPayer( 53 | _defaultSplitsProjectId, 54 | _defaultSplitsDomain, 55 | _defaultSplitsGroup, 56 | _splitsStore, 57 | _defaultProjectId, 58 | _defaultBeneficiary, 59 | _defaultPreferClaimedTokens, 60 | _defaultMemo, 61 | _defaultMetadata, 62 | _defaultPreferAddToBalance, 63 | _owner 64 | ); 65 | 66 | emit DeploySplitsPayer( 67 | splitsPayer, 68 | _defaultSplitsProjectId, 69 | _defaultSplitsDomain, 70 | _defaultSplitsGroup, 71 | _splitsStore, 72 | _defaultProjectId, 73 | _defaultBeneficiary, 74 | _defaultPreferClaimedTokens, 75 | _defaultMemo, 76 | _defaultMetadata, 77 | _defaultPreferAddToBalance, 78 | _owner, 79 | msg.sender 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/JBETHPaymentTerminal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import '@openzeppelin/contracts/utils/Address.sol'; 5 | import './abstract/JBPayoutRedemptionPaymentTerminal.sol'; 6 | 7 | /** 8 | @notice 9 | Manages all inflows and outflows of ETH funds into the protocol ecosystem. 10 | 11 | @dev 12 | Inherits from - 13 | JBPayoutRedemptionPaymentTerminal: Generic terminal managing all inflows and outflows of funds into the protocol ecosystem. 14 | */ 15 | contract JBETHPaymentTerminal is JBPayoutRedemptionPaymentTerminal { 16 | //*********************************************************************// 17 | // -------------------------- constructor ---------------------------- // 18 | //*********************************************************************// 19 | 20 | /** 21 | @param _baseWeightCurrency The currency to base token issuance on. 22 | @param _operatorStore A contract storing operator assignments. 23 | @param _projects A contract which mints ERC-721's that represent project ownership and transfers. 24 | @param _directory A contract storing directories of terminals and controllers for each project. 25 | @param _splitsStore A contract that stores splits for each project. 26 | @param _prices A contract that exposes price feeds. 27 | @param _store A contract that stores the terminal's data. 28 | @param _owner The address that will own this contract. 29 | */ 30 | constructor( 31 | uint256 _baseWeightCurrency, 32 | IJBOperatorStore _operatorStore, 33 | IJBProjects _projects, 34 | IJBDirectory _directory, 35 | IJBSplitsStore _splitsStore, 36 | IJBPrices _prices, 37 | IJBSingleTokenPaymentTerminalStore _store, 38 | address _owner 39 | ) 40 | JBPayoutRedemptionPaymentTerminal( 41 | JBTokens.ETH, 42 | 18, // 18 decimals. 43 | JBCurrencies.ETH, 44 | _baseWeightCurrency, 45 | JBSplitsGroups.ETH_PAYOUT, 46 | _operatorStore, 47 | _projects, 48 | _directory, 49 | _splitsStore, 50 | _prices, 51 | _store, 52 | _owner 53 | ) 54 | // solhint-disable-next-line no-empty-blocks 55 | { 56 | 57 | } 58 | 59 | //*********************************************************************// 60 | // ---------------------- internal transactions ---------------------- // 61 | //*********************************************************************// 62 | 63 | /** 64 | @notice 65 | Transfers tokens. 66 | 67 | @param _from The address from which the transfer should originate. 68 | @param _to The address to which the transfer should go. 69 | @param _amount The amount of the transfer, as a fixed point number with the same number of decimals as this terminal. 70 | */ 71 | function _transferFrom( 72 | address _from, 73 | address payable _to, 74 | uint256 _amount 75 | ) internal override { 76 | _from; // Prevents unused var compiler and natspec complaints. 77 | 78 | Address.sendValue(_to, _amount); 79 | } 80 | 81 | /** 82 | @notice 83 | Logic to be triggered before transferring tokens from this terminal. 84 | 85 | @param _to The address to which the transfer is going. 86 | @param _amount The amount of the transfer, as a fixed point number with the same number of decimals as this terminal. 87 | */ 88 | function _beforeTransferTo(address _to, uint256 _amount) internal pure override { 89 | _to; // Prevents unused var compiler and natspec complaints. 90 | _amount; // Prevents unused var compiler and natspec complaints. 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /contracts/abstract/JBControllerUtility.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import './../interfaces/IJBControllerUtility.sol'; 5 | 6 | /** 7 | @notice 8 | Provides tools for contracts with functionality that can only be accessed by a project's controller. 9 | 10 | @dev 11 | Adheres to - 12 | IJBControllerUtility: General interface for the methods in this contract that interact with the blockchain's state according to the protocol's rules. 13 | */ 14 | abstract contract JBControllerUtility is IJBControllerUtility { 15 | //*********************************************************************// 16 | // --------------------------- custom errors -------------------------- // 17 | //*********************************************************************// 18 | error CONTROLLER_UNAUTHORIZED(); 19 | 20 | //*********************************************************************// 21 | // ---------------------------- modifiers ---------------------------- // 22 | //*********************************************************************// 23 | 24 | /** 25 | @notice 26 | Only allows the controller of the specified project to proceed. 27 | 28 | @param _projectId The ID of the project. 29 | */ 30 | modifier onlyController(uint256 _projectId) { 31 | if (address(directory.controllerOf(_projectId)) != msg.sender) revert CONTROLLER_UNAUTHORIZED(); 32 | _; 33 | } 34 | 35 | //*********************************************************************// 36 | // ---------------- public immutable stored properties --------------- // 37 | //*********************************************************************// 38 | 39 | /** 40 | @notice 41 | The directory of terminals and controllers for projects. 42 | */ 43 | IJBDirectory public immutable override directory; 44 | 45 | //*********************************************************************// 46 | // -------------------------- constructor ---------------------------- // 47 | //*********************************************************************// 48 | 49 | /** 50 | @param _directory A contract storing directories of terminals and controllers for each project. 51 | */ 52 | constructor(IJBDirectory _directory) { 53 | directory = _directory; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/enums/JBBallotState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | enum JBBallotState { 5 | Active, 6 | Approved, 7 | Failed 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBAllowanceTerminal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBAllowanceTerminal { 5 | function useAllowanceOf( 6 | uint256 _projectId, 7 | uint256 _amount, 8 | uint256 _currency, 9 | address _token, 10 | uint256 _minReturnedTokens, 11 | address payable _beneficiary, 12 | string calldata _memo 13 | ) external returns (uint256 netDistributedAmount); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBControllerUtility.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBDirectory.sol'; 5 | 6 | interface IJBControllerUtility { 7 | function directory() external view returns (IJBDirectory); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBDirectory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBFundingCycleStore.sol'; 5 | import './IJBPaymentTerminal.sol'; 6 | import './IJBProjects.sol'; 7 | 8 | interface IJBDirectory { 9 | event SetController(uint256 indexed projectId, address indexed controller, address caller); 10 | 11 | event AddTerminal(uint256 indexed projectId, IJBPaymentTerminal indexed terminal, address caller); 12 | 13 | event SetTerminals(uint256 indexed projectId, IJBPaymentTerminal[] terminals, address caller); 14 | 15 | event SetPrimaryTerminal( 16 | uint256 indexed projectId, 17 | address indexed token, 18 | IJBPaymentTerminal indexed terminal, 19 | address caller 20 | ); 21 | 22 | event SetIsAllowedToSetFirstController(address indexed addr, bool indexed flag, address caller); 23 | 24 | function projects() external view returns (IJBProjects); 25 | 26 | function fundingCycleStore() external view returns (IJBFundingCycleStore); 27 | 28 | function controllerOf(uint256 _projectId) external view returns (address); 29 | 30 | function isAllowedToSetFirstController(address _address) external view returns (bool); 31 | 32 | function terminalsOf(uint256 _projectId) external view returns (IJBPaymentTerminal[] memory); 33 | 34 | function isTerminalOf(uint256 _projectId, IJBPaymentTerminal _terminal) 35 | external 36 | view 37 | returns (bool); 38 | 39 | function primaryTerminalOf(uint256 _projectId, address _token) 40 | external 41 | view 42 | returns (IJBPaymentTerminal); 43 | 44 | function setControllerOf(uint256 _projectId, address _controller) external; 45 | 46 | function setTerminalsOf(uint256 _projectId, IJBPaymentTerminal[] calldata _terminals) external; 47 | 48 | function setPrimaryTerminalOf( 49 | uint256 _projectId, 50 | address _token, 51 | IJBPaymentTerminal _terminal 52 | ) external; 53 | 54 | function setIsAllowedToSetFirstController(address _address, bool _flag) external; 55 | } 56 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBETHERC20ProjectPayerDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBDirectory.sol'; 5 | import './IJBProjectPayer.sol'; 6 | 7 | interface IJBETHERC20ProjectPayerDeployer { 8 | event DeployProjectPayer( 9 | IJBProjectPayer indexed projectPayer, 10 | uint256 defaultProjectId, 11 | address defaultBeneficiary, 12 | bool defaultPreferClaimedTokens, 13 | string defaultMemo, 14 | bytes defaultMetadata, 15 | bool preferAddToBalance, 16 | IJBDirectory directory, 17 | address owner, 18 | address caller 19 | ); 20 | 21 | function deployProjectPayer( 22 | uint256 _defaultProjectId, 23 | address payable _defaultBeneficiary, 24 | bool _defaultPreferClaimedTokens, 25 | string memory _defaultMemo, 26 | bytes memory _defaultMetadata, 27 | bool _preferAddToBalance, 28 | IJBDirectory _directory, 29 | address _owner 30 | ) external returns (IJBProjectPayer projectPayer); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBETHERC20SplitsPayerDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBSplitsPayer.sol'; 5 | import './IJBSplitsStore.sol'; 6 | 7 | interface IJBETHERC20SplitsPayerDeployer { 8 | event DeploySplitsPayer( 9 | IJBSplitsPayer indexed splitsPayer, 10 | uint256 defaultSplitsProjectId, 11 | uint256 defaultSplitsDomain, 12 | uint256 defaultSplitsGroup, 13 | IJBSplitsStore splitsStore, 14 | uint256 defaultProjectId, 15 | address defaultBeneficiary, 16 | bool defaultPreferClaimedTokens, 17 | string defaultMemo, 18 | bytes defaultMetadata, 19 | bool preferAddToBalance, 20 | address owner, 21 | address caller 22 | ); 23 | 24 | function deploySplitsPayer( 25 | uint256 _defaultSplitsProjectId, 26 | uint256 _defaultSplitsDomain, 27 | uint256 _defaultSplitsGroup, 28 | IJBSplitsStore _splitsStore, 29 | uint256 _defaultProjectId, 30 | address payable _defaultBeneficiary, 31 | bool _defaultPreferClaimedTokens, 32 | string calldata _defaultMemo, 33 | bytes calldata _defaultMetadata, 34 | bool _preferAddToBalance, 35 | address _owner 36 | ) external returns (IJBSplitsPayer splitsPayer); 37 | } 38 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBFeeGauge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBFeeGauge { 5 | function currentDiscountFor(uint256 _projectId) external view returns (uint256); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBFundingCycleBallot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | import './../enums/JBBallotState.sol'; 6 | import './IJBFundingCycleStore.sol'; 7 | 8 | interface IJBFundingCycleBallot is IERC165 { 9 | function duration() external view returns (uint256); 10 | 11 | function stateOf( 12 | uint256 _projectId, 13 | uint256 _configuration, 14 | uint256 _start 15 | ) external view returns (JBBallotState); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBFundingCycleDataSource.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | import './../structs/JBPayParamsData.sol'; 6 | import './../structs/JBRedeemParamsData.sol'; 7 | import './IJBFundingCycleStore.sol'; 8 | import './IJBPayDelegate.sol'; 9 | import './IJBRedemptionDelegate.sol'; 10 | 11 | /** 12 | @title 13 | Datasource 14 | 15 | @notice 16 | The datasource is called by JBPaymentTerminal on pay and redemption, and provide an extra layer of logic to use 17 | a custom weight, a custom memo and/or a pay/redeem delegate 18 | 19 | @dev 20 | Adheres to: 21 | IERC165 for adequate interface integration 22 | */ 23 | interface IJBFundingCycleDataSource is IERC165 { 24 | /** 25 | @notice 26 | The datasource implementation for JBPaymentTerminal.pay(..) 27 | 28 | @param _data the data passed to the data source in terminal.pay(..), as a JBPayParamsData struct: 29 | IJBPaymentTerminal terminal; 30 | address payer; 31 | JBTokenAmount amount; 32 | uint256 projectId; 33 | uint256 currentFundingCycleConfiguration; 34 | address beneficiary; 35 | uint256 weight; 36 | uint256 reservedRate; 37 | string memo; 38 | bytes metadata; 39 | 40 | @return weight the weight to use to override the funding cycle weight 41 | @return memo the memo to override the pay(..) memo 42 | @return delegate the address of the pay delegate (might or might not be the same contract) 43 | */ 44 | function payParams(JBPayParamsData calldata _data) 45 | external 46 | returns ( 47 | uint256 weight, 48 | string memory memo, 49 | IJBPayDelegate delegate 50 | ); 51 | 52 | /** 53 | @notice 54 | The datasource implementation for JBPaymentTerminal.redeemTokensOf(..) 55 | 56 | @param _data the data passed to the data source in terminal.redeemTokensOf(..), as a JBRedeemParamsData struct: 57 | IJBPaymentTerminal terminal; 58 | address holder; 59 | uint256 projectId; 60 | uint256 currentFundingCycleConfiguration; 61 | uint256 tokenCount; 62 | uint256 totalSupply; 63 | uint256 overflow; 64 | JBTokenAmount reclaimAmount; 65 | bool useTotalOverflow; 66 | uint256 redemptionRate; 67 | uint256 ballotRedemptionRate; 68 | string memo; 69 | bytes metadata; 70 | 71 | @return reclaimAmount the amount to claim, overriding the terminal logic 72 | @return memo the memo to override the redeemTokensOf(..) memo 73 | @return delegate the address of the redemption delegate (might or might not be the same contract) 74 | */ 75 | function redeemParams(JBRedeemParamsData calldata _data) 76 | external 77 | returns ( 78 | uint256 reclaimAmount, 79 | string memory memo, 80 | IJBRedemptionDelegate delegate 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBFundingCycleStore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../enums/JBBallotState.sol'; 5 | import './../structs/JBFundingCycle.sol'; 6 | import './../structs/JBFundingCycleData.sol'; 7 | 8 | interface IJBFundingCycleStore { 9 | event Configure( 10 | uint256 indexed configuration, 11 | uint256 indexed projectId, 12 | JBFundingCycleData data, 13 | uint256 metadata, 14 | uint256 mustStartAtOrAfter, 15 | address caller 16 | ); 17 | 18 | event Init(uint256 indexed configuration, uint256 indexed projectId, uint256 indexed basedOn); 19 | 20 | function latestConfigurationOf(uint256 _projectId) external view returns (uint256); 21 | 22 | function get(uint256 _projectId, uint256 _configuration) 23 | external 24 | view 25 | returns (JBFundingCycle memory); 26 | 27 | function latestConfiguredOf(uint256 _projectId) 28 | external 29 | view 30 | returns (JBFundingCycle memory fundingCycle, JBBallotState ballotState); 31 | 32 | function queuedOf(uint256 _projectId) external view returns (JBFundingCycle memory fundingCycle); 33 | 34 | function currentOf(uint256 _projectId) external view returns (JBFundingCycle memory fundingCycle); 35 | 36 | function currentBallotStateOf(uint256 _projectId) external view returns (JBBallotState); 37 | 38 | function configureFor( 39 | uint256 _projectId, 40 | JBFundingCycleData calldata _data, 41 | uint256 _metadata, 42 | uint256 _mustStartAtOrAfter 43 | ) external returns (JBFundingCycle memory fundingCycle); 44 | } 45 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBMigratable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBMigratable { 5 | function prepForMigrationOf(uint256 _projectId, address _from) external; 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBOperatable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBOperatorStore.sol'; 5 | 6 | interface IJBOperatable { 7 | function operatorStore() external view returns (IJBOperatorStore); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBOperatorStore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../structs/JBOperatorData.sol'; 5 | 6 | interface IJBOperatorStore { 7 | event SetOperator( 8 | address indexed operator, 9 | address indexed account, 10 | uint256 indexed domain, 11 | uint256[] permissionIndexes, 12 | uint256 packed 13 | ); 14 | 15 | function permissionsOf( 16 | address _operator, 17 | address _account, 18 | uint256 _domain 19 | ) external view returns (uint256); 20 | 21 | function hasPermission( 22 | address _operator, 23 | address _account, 24 | uint256 _domain, 25 | uint256 _permissionIndex 26 | ) external view returns (bool); 27 | 28 | function hasPermissions( 29 | address _operator, 30 | address _account, 31 | uint256 _domain, 32 | uint256[] calldata _permissionIndexes 33 | ) external view returns (bool); 34 | 35 | function setOperator(JBOperatorData calldata _operatorData) external; 36 | 37 | function setOperators(JBOperatorData[] calldata _operatorData) external; 38 | } 39 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBPayDelegate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | import './../structs/JBDidPayData.sol'; 6 | 7 | /** 8 | @title 9 | Pay delegate 10 | 11 | @notice 12 | Delegate called after JBTerminal.pay(..) logic completion (if passed by the funding cycle datasource) 13 | 14 | @dev 15 | Adheres to: 16 | IERC165 for adequate interface integration 17 | */ 18 | interface IJBPayDelegate is IERC165 { 19 | /** 20 | @notice 21 | This function is called by JBPaymentTerminal.pay(..), after the execution of its logic 22 | 23 | @dev 24 | Critical business logic should be protected by an appropriate access control 25 | 26 | @param _data the data passed by the terminal, as a JBDidPayData struct: 27 | address payer; 28 | uint256 projectId; 29 | uint256 currentFundingCycleConfiguration; 30 | JBTokenAmount amount; 31 | uint256 projectTokenCount; 32 | address beneficiary; 33 | bool preferClaimedTokens; 34 | string memo; 35 | bytes metadata; 36 | */ 37 | function didPay(JBDidPayData calldata _data) external; 38 | } 39 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBPaymentTerminal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | 6 | interface IJBPaymentTerminal is IERC165 { 7 | function acceptsToken(address _token, uint256 _projectId) external view returns (bool); 8 | 9 | function currencyForToken(address _token) external view returns (uint256); 10 | 11 | function decimalsForToken(address _token) external view returns (uint256); 12 | 13 | // Return value must be a fixed point number with 18 decimals. 14 | function currentEthOverflowOf(uint256 _projectId) external view returns (uint256); 15 | 16 | function pay( 17 | uint256 _projectId, 18 | uint256 _amount, 19 | address _token, 20 | address _beneficiary, 21 | uint256 _minReturnedTokens, 22 | bool _preferClaimedTokens, 23 | string calldata _memo, 24 | bytes calldata _metadata 25 | ) external payable returns (uint256 beneficiaryTokenCount); 26 | 27 | function addToBalanceOf( 28 | uint256 _projectId, 29 | uint256 _amount, 30 | address _token, 31 | string calldata _memo, 32 | bytes calldata _metadata 33 | ) external payable; 34 | } 35 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBPayoutTerminal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBPayoutTerminal { 5 | function distributePayoutsOf( 6 | uint256 _projectId, 7 | uint256 _amount, 8 | uint256 _currency, 9 | address _token, 10 | uint256 _minReturnedTokens, 11 | string calldata _memo 12 | ) external returns (uint256 netLeftoverDistributionAmount); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBPriceFeed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBPriceFeed { 5 | function currentPrice(uint256 _targetDecimals) external view returns (uint256); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBPrices.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBPriceFeed.sol'; 5 | 6 | interface IJBPrices { 7 | event AddFeed(uint256 indexed currency, uint256 indexed base, IJBPriceFeed feed); 8 | 9 | function feedFor(uint256 _currency, uint256 _base) external view returns (IJBPriceFeed); 10 | 11 | function priceFor( 12 | uint256 _currency, 13 | uint256 _base, 14 | uint256 _decimals 15 | ) external view returns (uint256); 16 | 17 | function addFeedFor( 18 | uint256 _currency, 19 | uint256 _base, 20 | IJBPriceFeed _priceFeed 21 | ) external; 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBProjectPayer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | import './IJBDirectory.sol'; 6 | 7 | interface IJBProjectPayer is IERC165 { 8 | event SetDefaultValues( 9 | uint256 indexed projectId, 10 | address indexed beneficiary, 11 | bool preferClaimedTokens, 12 | string memo, 13 | bytes metadata, 14 | bool preferAddToBalance, 15 | address caller 16 | ); 17 | 18 | function directory() external view returns (IJBDirectory); 19 | 20 | function defaultProjectId() external view returns (uint256); 21 | 22 | function defaultBeneficiary() external view returns (address payable); 23 | 24 | function defaultPreferClaimedTokens() external view returns (bool); 25 | 26 | function defaultMemo() external view returns (string memory); 27 | 28 | function defaultMetadata() external view returns (bytes memory); 29 | 30 | function defaultPreferAddToBalance() external view returns (bool); 31 | 32 | function setDefaultValues( 33 | uint256 _projectId, 34 | address payable _beneficiary, 35 | bool _preferClaimedTokens, 36 | string memory _memo, 37 | bytes memory _metadata, 38 | bool _defaultPreferAddToBalance 39 | ) external; 40 | 41 | function pay( 42 | uint256 _projectId, 43 | address _token, 44 | uint256 _amount, 45 | uint256 _decimals, 46 | address _beneficiary, 47 | uint256 _minReturnedTokens, 48 | bool _preferClaimedTokens, 49 | string memory _memo, 50 | bytes memory _metadata 51 | ) external payable; 52 | 53 | function addToBalanceOf( 54 | uint256 _projectId, 55 | address _token, 56 | uint256 _amount, 57 | uint256 _decimals, 58 | string memory _memo, 59 | bytes memory _metadata 60 | ) external payable; 61 | 62 | receive() external payable; 63 | } 64 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBProjects.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; 5 | import './../structs/JBProjectMetadata.sol'; 6 | import './IJBTokenUriResolver.sol'; 7 | 8 | interface IJBProjects is IERC721 { 9 | event Create( 10 | uint256 indexed projectId, 11 | address indexed owner, 12 | JBProjectMetadata metadata, 13 | address caller 14 | ); 15 | 16 | event SetMetadata(uint256 indexed projectId, JBProjectMetadata metadata, address caller); 17 | 18 | event SetTokenUriResolver(IJBTokenUriResolver indexed resolver, address caller); 19 | 20 | function count() external view returns (uint256); 21 | 22 | function metadataContentOf(uint256 _projectId, uint256 _domain) 23 | external 24 | view 25 | returns (string memory); 26 | 27 | function tokenUriResolver() external view returns (IJBTokenUriResolver); 28 | 29 | function createFor(address _owner, JBProjectMetadata calldata _metadata) 30 | external 31 | returns (uint256 projectId); 32 | 33 | function setMetadataOf(uint256 _projectId, JBProjectMetadata calldata _metadata) external; 34 | 35 | function setTokenUriResolver(IJBTokenUriResolver _newResolver) external; 36 | } 37 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBReconfigurationBufferBallot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBFundingCycleBallot.sol'; 5 | 6 | interface IJBReconfigurationBufferBallot is IJBFundingCycleBallot { 7 | event Finalize( 8 | uint256 indexed projectId, 9 | uint256 indexed configuration, 10 | JBBallotState indexed ballotState, 11 | address caller 12 | ); 13 | 14 | function finalState(uint256 _projectId, uint256 _configuration) 15 | external 16 | view 17 | returns (JBBallotState); 18 | 19 | function fundingCycleStore() external view returns (IJBFundingCycleStore); 20 | 21 | function finalize(uint256 _projectId, uint256 _configured) external returns (JBBallotState); 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBRedemptionDelegate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | import './../structs/JBDidRedeemData.sol'; 6 | 7 | /** 8 | @title 9 | Redemption delegate 10 | 11 | @notice 12 | Delegate called after JBTerminal.redeemTokensOf(..) logic completion (if passed by the funding cycle datasource) 13 | 14 | @dev 15 | Adheres to: 16 | IERC165 for adequate interface integration 17 | */ 18 | interface IJBRedemptionDelegate is IERC165 { 19 | /** 20 | @notice 21 | This function is called by JBPaymentTerminal.redeemTokensOf(..), after the execution of its logic 22 | 23 | @dev 24 | Critical business logic should be protected by an appropriate access control 25 | 26 | @param _data the data passed by the terminal, as a JBDidRedeemData struct: 27 | address holder; 28 | uint256 projectId; 29 | uint256 currentFundingCycleConfiguration; 30 | uint256 projectTokenCount; 31 | JBTokenAmount reclaimedAmount; 32 | address payable beneficiary; 33 | string memo; 34 | bytes metadata; 35 | */ 36 | function didRedeem(JBDidRedeemData calldata _data) external; 37 | } 38 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBRedemptionTerminal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBRedemptionTerminal { 5 | function redeemTokensOf( 6 | address _holder, 7 | uint256 _projectId, 8 | uint256 _tokenCount, 9 | address _token, 10 | uint256 _minReturnedTokens, 11 | address payable _beneficiary, 12 | string calldata _memo, 13 | bytes calldata _metadata 14 | ) external returns (uint256 reclaimAmount); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBSingleTokenPaymentTerminal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBPaymentTerminal.sol'; 5 | 6 | interface IJBSingleTokenPaymentTerminal is IJBPaymentTerminal { 7 | function token() external view returns (address); 8 | 9 | function currency() external view returns (uint256); 10 | 11 | function decimals() external view returns (uint256); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBSingleTokenPaymentTerminalStore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../structs/JBFundingCycle.sol'; 5 | import './../structs/JBTokenAmount.sol'; 6 | import './IJBDirectory.sol'; 7 | import './IJBFundingCycleStore.sol'; 8 | import './IJBPayDelegate.sol'; 9 | import './IJBPrices.sol'; 10 | import './IJBRedemptionDelegate.sol'; 11 | import './IJBSingleTokenPaymentTerminal.sol'; 12 | 13 | interface IJBSingleTokenPaymentTerminalStore { 14 | function fundingCycleStore() external view returns (IJBFundingCycleStore); 15 | 16 | function directory() external view returns (IJBDirectory); 17 | 18 | function prices() external view returns (IJBPrices); 19 | 20 | function balanceOf(IJBSingleTokenPaymentTerminal _terminal, uint256 _projectId) 21 | external 22 | view 23 | returns (uint256); 24 | 25 | function usedDistributionLimitOf( 26 | IJBSingleTokenPaymentTerminal _terminal, 27 | uint256 _projectId, 28 | uint256 _fundingCycleNumber 29 | ) external view returns (uint256); 30 | 31 | function usedOverflowAllowanceOf( 32 | IJBSingleTokenPaymentTerminal _terminal, 33 | uint256 _projectId, 34 | uint256 _fundingCycleConfiguration 35 | ) external view returns (uint256); 36 | 37 | function currentOverflowOf(IJBSingleTokenPaymentTerminal _terminal, uint256 _projectId) 38 | external 39 | view 40 | returns (uint256); 41 | 42 | function currentTotalOverflowOf( 43 | uint256 _projectId, 44 | uint256 _decimals, 45 | uint256 _currency 46 | ) external view returns (uint256); 47 | 48 | function currentReclaimableOverflowOf( 49 | IJBSingleTokenPaymentTerminal _terminal, 50 | uint256 _projectId, 51 | uint256 _tokenCount, 52 | bool _useTotalOverflow 53 | ) external view returns (uint256); 54 | 55 | function currentReclaimableOverflowOf( 56 | uint256 _projectId, 57 | uint256 _tokenCount, 58 | uint256 _totalSupply, 59 | uint256 _overflow 60 | ) external view returns (uint256); 61 | 62 | function recordPaymentFrom( 63 | address _payer, 64 | JBTokenAmount memory _amount, 65 | uint256 _projectId, 66 | uint256 _baseWeightCurrency, 67 | address _beneficiary, 68 | string calldata _memo, 69 | bytes calldata _metadata 70 | ) 71 | external 72 | returns ( 73 | JBFundingCycle memory fundingCycle, 74 | uint256 tokenCount, 75 | IJBPayDelegate delegate, 76 | string memory memo 77 | ); 78 | 79 | function recordRedemptionFor( 80 | address _holder, 81 | uint256 _projectId, 82 | uint256 _tokenCount, 83 | string calldata _memo, 84 | bytes calldata _metadata 85 | ) 86 | external 87 | returns ( 88 | JBFundingCycle memory fundingCycle, 89 | uint256 reclaimAmount, 90 | IJBRedemptionDelegate delegate, 91 | string memory memo 92 | ); 93 | 94 | function recordDistributionFor( 95 | uint256 _projectId, 96 | uint256 _amount, 97 | uint256 _currency 98 | ) external returns (JBFundingCycle memory fundingCycle, uint256 distributedAmount); 99 | 100 | function recordUsedAllowanceOf( 101 | uint256 _projectId, 102 | uint256 _amount, 103 | uint256 _currency 104 | ) external returns (JBFundingCycle memory fundingCycle, uint256 withdrawnAmount); 105 | 106 | function recordAddedBalanceFor(uint256 _projectId, uint256 _amount) external; 107 | 108 | function recordMigration(uint256 _projectId) external returns (uint256 balance); 109 | } 110 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBSplitAllocator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | import '../structs/JBSplitAllocationData.sol'; 6 | 7 | /** 8 | @title 9 | Split allocator 10 | 11 | @notice 12 | Provide a way to process a single split with extra logic 13 | 14 | @dev 15 | Adheres to: 16 | IERC165 for adequate interface integration 17 | 18 | @dev 19 | The contract address should be set as an allocator in the adequate split 20 | */ 21 | interface IJBSplitAllocator is IERC165 { 22 | /** 23 | @notice 24 | This function is called by JBPaymentTerminal.distributePayoutOf(..), during the processing of the split including it 25 | 26 | @dev 27 | Critical business logic should be protected by an appropriate access control. The token and/or eth are optimistically transfered 28 | to the allocator for its logic. 29 | 30 | @param _data the data passed by the terminal, as a JBSplitAllocationData struct: 31 | address token; 32 | uint256 amount; 33 | uint256 decimals; 34 | uint256 projectId; 35 | uint256 group; 36 | JBSplit split; 37 | */ 38 | function allocate(JBSplitAllocationData calldata _data) external payable; 39 | } 40 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBSplitsPayer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | import './../structs/JBSplit.sol'; 6 | import './IJBSplitsStore.sol'; 7 | 8 | interface IJBSplitsPayer is IERC165 { 9 | event SetDefaultSplits( 10 | uint256 indexed projectId, 11 | uint256 indexed domain, 12 | uint256 indexed group, 13 | address caller 14 | ); 15 | event Pay( 16 | uint256 indexed projectId, 17 | address beneficiary, 18 | address token, 19 | uint256 amount, 20 | uint256 decimals, 21 | uint256 leftoverAmount, 22 | uint256 minReturnedTokens, 23 | bool preferClaimedTokens, 24 | string memo, 25 | bytes metadata, 26 | address caller 27 | ); 28 | 29 | event AddToBalance( 30 | uint256 indexed projectId, 31 | address beneficiary, 32 | address token, 33 | uint256 amount, 34 | uint256 decimals, 35 | uint256 leftoverAmount, 36 | string memo, 37 | bytes metadata, 38 | address caller 39 | ); 40 | 41 | event DistributeToSplitGroup( 42 | uint256 indexed projectId, 43 | uint256 indexed domain, 44 | uint256 indexed group, 45 | address caller 46 | ); 47 | 48 | event DistributeToSplit( 49 | JBSplit split, 50 | uint256 amount, 51 | address defaultBeneficiary, 52 | address caller 53 | ); 54 | 55 | function defaultSplitsProjectId() external view returns (uint256); 56 | 57 | function defaultSplitsDomain() external view returns (uint256); 58 | 59 | function defaultSplitsGroup() external view returns (uint256); 60 | 61 | function splitsStore() external view returns (IJBSplitsStore); 62 | 63 | function setDefaultSplits( 64 | uint256 _projectId, 65 | uint256 _domain, 66 | uint256 _group 67 | ) external; 68 | } 69 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBSplitsStore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../structs/JBGroupedSplits.sol'; 5 | import './../structs/JBSplit.sol'; 6 | import './IJBDirectory.sol'; 7 | import './IJBProjects.sol'; 8 | 9 | interface IJBSplitsStore { 10 | event SetSplit( 11 | uint256 indexed projectId, 12 | uint256 indexed domain, 13 | uint256 indexed group, 14 | JBSplit split, 15 | address caller 16 | ); 17 | 18 | function projects() external view returns (IJBProjects); 19 | 20 | function directory() external view returns (IJBDirectory); 21 | 22 | function splitsOf( 23 | uint256 _projectId, 24 | uint256 _domain, 25 | uint256 _group 26 | ) external view returns (JBSplit[] memory); 27 | 28 | function set( 29 | uint256 _projectId, 30 | uint256 _domain, 31 | JBGroupedSplits[] memory _groupedSplits 32 | ) external; 33 | } 34 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBTerminalUtility.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBDirectory.sol'; 5 | 6 | interface IJBPaymentTerminalUtility { 7 | function directory() external view returns (IJBDirectory); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBToken { 5 | function decimals() external view returns (uint8); 6 | 7 | function totalSupply(uint256 _projectId) external view returns (uint256); 8 | 9 | function balanceOf(address _account, uint256 _projectId) external view returns (uint256); 10 | 11 | function mint( 12 | uint256 _projectId, 13 | address _account, 14 | uint256 _amount 15 | ) external; 16 | 17 | function burn( 18 | uint256 _projectId, 19 | address _account, 20 | uint256 _amount 21 | ) external; 22 | 23 | function approve( 24 | uint256, 25 | address _spender, 26 | uint256 _amount 27 | ) external; 28 | 29 | function transfer( 30 | uint256 _projectId, 31 | address _to, 32 | uint256 _amount 33 | ) external; 34 | 35 | function transferFrom( 36 | uint256 _projectId, 37 | address _from, 38 | address _to, 39 | uint256 _amount 40 | ) external; 41 | 42 | function transferOwnership(uint256 _projectId, address _newOwner) external; 43 | } 44 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBTokenStore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './IJBProjects.sol'; 5 | import './IJBToken.sol'; 6 | 7 | interface IJBTokenStore { 8 | event Issue( 9 | uint256 indexed projectId, 10 | IJBToken indexed token, 11 | string name, 12 | string symbol, 13 | address caller 14 | ); 15 | 16 | event Mint( 17 | address indexed holder, 18 | uint256 indexed projectId, 19 | uint256 amount, 20 | bool tokensWereClaimed, 21 | bool preferClaimedTokens, 22 | address caller 23 | ); 24 | 25 | event Burn( 26 | address indexed holder, 27 | uint256 indexed projectId, 28 | uint256 amount, 29 | uint256 initialUnclaimedBalance, 30 | uint256 initialClaimedBalance, 31 | bool preferClaimedTokens, 32 | address caller 33 | ); 34 | 35 | event Claim( 36 | address indexed holder, 37 | uint256 indexed projectId, 38 | uint256 initialUnclaimedBalance, 39 | uint256 amount, 40 | address caller 41 | ); 42 | 43 | event ShouldRequireClaim(uint256 indexed projectId, bool indexed flag, address caller); 44 | 45 | event Change( 46 | uint256 indexed projectId, 47 | IJBToken indexed newToken, 48 | IJBToken indexed oldToken, 49 | address owner, 50 | address caller 51 | ); 52 | 53 | event Transfer( 54 | address indexed holder, 55 | uint256 indexed projectId, 56 | address indexed recipient, 57 | uint256 amount, 58 | address caller 59 | ); 60 | 61 | function tokenOf(uint256 _projectId) external view returns (IJBToken); 62 | 63 | function projectOf(IJBToken _token) external view returns (uint256); 64 | 65 | function projects() external view returns (IJBProjects); 66 | 67 | function unclaimedBalanceOf(address _holder, uint256 _projectId) external view returns (uint256); 68 | 69 | function unclaimedTotalSupplyOf(uint256 _projectId) external view returns (uint256); 70 | 71 | function totalSupplyOf(uint256 _projectId) external view returns (uint256); 72 | 73 | function balanceOf(address _holder, uint256 _projectId) external view returns (uint256 _result); 74 | 75 | function requireClaimFor(uint256 _projectId) external view returns (bool); 76 | 77 | function issueFor( 78 | uint256 _projectId, 79 | string calldata _name, 80 | string calldata _symbol 81 | ) external returns (IJBToken token); 82 | 83 | function changeFor( 84 | uint256 _projectId, 85 | IJBToken _token, 86 | address _newOwner 87 | ) external returns (IJBToken oldToken); 88 | 89 | function burnFrom( 90 | address _holder, 91 | uint256 _projectId, 92 | uint256 _amount, 93 | bool _preferClaimedTokens 94 | ) external; 95 | 96 | function mintFor( 97 | address _holder, 98 | uint256 _projectId, 99 | uint256 _amount, 100 | bool _preferClaimedTokens 101 | ) external; 102 | 103 | function shouldRequireClaimingFor(uint256 _projectId, bool _flag) external; 104 | 105 | function claimFor( 106 | address _holder, 107 | uint256 _projectId, 108 | uint256 _amount 109 | ) external; 110 | 111 | function transferFrom( 112 | address _holder, 113 | uint256 _projectId, 114 | address _recipient, 115 | uint256 _amount 116 | ) external; 117 | } 118 | -------------------------------------------------------------------------------- /contracts/interfaces/IJBTokenUriResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IJBTokenUriResolver { 5 | function getUri(uint256 _projectId) external view returns (string memory tokenUri); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/libraries/JBConstants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | @notice 6 | Global constants used across Juicebox contracts. 7 | */ 8 | library JBConstants { 9 | uint256 public constant MAX_RESERVED_RATE = 10000; 10 | uint256 public constant MAX_REDEMPTION_RATE = 10000; 11 | uint256 public constant MAX_DISCOUNT_RATE = 1000000000; 12 | uint256 public constant SPLITS_TOTAL_PERCENT = 1000000000; 13 | uint256 public constant MAX_FEE = 1000000000; 14 | uint256 public constant MAX_FEE_DISCOUNT = 1000000000; 15 | } 16 | -------------------------------------------------------------------------------- /contracts/libraries/JBCurrencies.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library JBCurrencies { 5 | uint256 public constant ETH = 1; 6 | uint256 public constant USD = 2; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/libraries/JBFixedPointNumber.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library JBFixedPointNumber { 5 | function adjustDecimals( 6 | uint256 _value, 7 | uint256 _decimals, 8 | uint256 _targetDecimals 9 | ) internal pure returns (uint256) { 10 | // If decimals need adjusting, multiply or divide the price by the decimal adjuster to get the normalized result. 11 | if (_targetDecimals == _decimals) return _value; 12 | else if (_targetDecimals > _decimals) return _value * 10**(_targetDecimals - _decimals); 13 | else return _value / 10**(_decimals - _targetDecimals); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/libraries/JBGlobalFundingCycleMetadataResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBFundingCycleDataSource.sol'; 5 | import './../structs/JBFundingCycleMetadata.sol'; 6 | import './JBConstants.sol'; 7 | 8 | library JBGlobalFundingCycleMetadataResolver { 9 | function setTerminalsAllowed(uint8 _data) internal pure returns (bool) { 10 | return (_data & 1) == 1; 11 | } 12 | 13 | function setControllerAllowed(uint8 _data) internal pure returns (bool) { 14 | return ((_data >> 1) & 1) == 1; 15 | } 16 | 17 | /** 18 | @notice 19 | Pack the global funding cycle metadata. 20 | 21 | @param _metadata The metadata to validate and pack. 22 | 23 | @return packed The packed uint256 of all global metadata params. The first 8 bits specify the version. 24 | */ 25 | function packFundingCycleGlobalMetadata(JBGlobalFundingCycleMetadata memory _metadata) 26 | internal 27 | pure 28 | returns (uint256 packed) 29 | { 30 | // allow set terminals in bit 0. 31 | if (_metadata.allowSetTerminals) packed |= 1; 32 | // allow set controller in bit 1. 33 | if (_metadata.allowSetController) packed |= 1 << 1; 34 | } 35 | 36 | /** 37 | @notice 38 | Expand the global funding cycle metadata. 39 | 40 | @param _packedMetadata The packed metadata to expand. 41 | 42 | @return metadata The global metadata object. 43 | */ 44 | function expandMetadata(uint8 _packedMetadata) 45 | internal 46 | pure 47 | returns (JBGlobalFundingCycleMetadata memory metadata) 48 | { 49 | return 50 | JBGlobalFundingCycleMetadata( 51 | setTerminalsAllowed(_packedMetadata), 52 | setControllerAllowed(_packedMetadata) 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/libraries/JBOperations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library JBOperations { 5 | uint256 public constant RECONFIGURE = 1; 6 | uint256 public constant REDEEM = 2; 7 | uint256 public constant MIGRATE_CONTROLLER = 3; 8 | uint256 public constant MIGRATE_TERMINAL = 4; 9 | uint256 public constant PROCESS_FEES = 5; 10 | uint256 public constant SET_METADATA = 6; 11 | uint256 public constant ISSUE = 7; 12 | uint256 public constant CHANGE_TOKEN = 8; 13 | uint256 public constant MINT = 9; 14 | uint256 public constant BURN = 10; 15 | uint256 public constant CLAIM = 11; 16 | uint256 public constant TRANSFER = 12; 17 | uint256 public constant REQUIRE_CLAIM = 13; 18 | uint256 public constant SET_CONTROLLER = 14; 19 | uint256 public constant SET_TERMINALS = 15; 20 | uint256 public constant SET_PRIMARY_TERMINAL = 16; 21 | uint256 public constant USE_ALLOWANCE = 17; 22 | uint256 public constant SET_SPLITS = 18; 23 | } 24 | -------------------------------------------------------------------------------- /contracts/libraries/JBSplitsGroups.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library JBSplitsGroups { 5 | uint256 public constant ETH_PAYOUT = 1; 6 | uint256 public constant RESERVED_TOKENS = 2; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/libraries/JBTokens.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library JBTokens { 5 | /** 6 | @notice 7 | The ETH token address in Juicebox is represented by 0x000000000000000000000000000000000000EEEe. 8 | */ 9 | address public constant ETH = address(0x000000000000000000000000000000000000EEEe); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/structs/JBDidPayData.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './JBTokenAmount.sol'; 5 | 6 | /** 7 | @member payer The address from which the payment originated. 8 | @member projectId The ID of the project for which the payment was made. 9 | @member currentFundingCycleConfiguration The configuration of the funding cycle during which the payment is being made. 10 | @member amount The amount of the payment. Includes the token being paid, the value, the number of decimals included, and the currency of the amount. 11 | @member projectTokenCount The number of project tokens minted for the beneficiary. 12 | @member beneficiary The address to which the tokens were minted. 13 | @member preferClaimedTokens A flag indicating whether the request prefered to mint project tokens into the beneficiaries wallet rather than leaving them unclaimed. This is only possible if the project has an attached token contract. 14 | @member memo The memo that is being emitted alongside the payment. 15 | @member metadata Extra data to send to the delegate. 16 | */ 17 | struct JBDidPayData { 18 | address payer; 19 | uint256 projectId; 20 | uint256 currentFundingCycleConfiguration; 21 | JBTokenAmount amount; 22 | uint256 projectTokenCount; 23 | address beneficiary; 24 | bool preferClaimedTokens; 25 | string memo; 26 | bytes metadata; 27 | } 28 | -------------------------------------------------------------------------------- /contracts/structs/JBDidRedeemData.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './JBTokenAmount.sol'; 5 | 6 | /** 7 | @member holder The holder of the tokens being redeemed. 8 | @member projectId The ID of the project with which the redeemed tokens are associated. 9 | @member currentFundingCycleConfiguration The configuration of the funding cycle during which the redemption is being made. 10 | @member projectTokenCount The number of project tokens being redeemed. 11 | @member reclaimedAmount The amount reclaimed from the treasury. Includes the token being reclaimed, the value, the number of decimals included, and the currency of the amount. 12 | @member beneficiary The address to which the reclaimed amount will be sent. 13 | @member memo The memo that is being emitted alongside the redemption. 14 | @member metadata Extra data to send to the delegate. 15 | */ 16 | struct JBDidRedeemData { 17 | address holder; 18 | uint256 projectId; 19 | uint256 currentFundingCycleConfiguration; 20 | uint256 projectTokenCount; 21 | JBTokenAmount reclaimedAmount; 22 | address payable beneficiary; 23 | string memo; 24 | bytes metadata; 25 | } 26 | -------------------------------------------------------------------------------- /contracts/structs/JBFee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | @member amount The total amount the fee was taken from, as a fixed point number with the same number of decimals as the terminal in which this struct was created. 6 | @member fee The percent of the fee, out of MAX_FEE. 7 | @member feeDiscount The discount of the fee. 8 | @member beneficiary The address that will receive the tokens that are minted as a result of the fee payment. 9 | */ 10 | struct JBFee { 11 | uint256 amount; 12 | uint32 fee; 13 | uint32 feeDiscount; 14 | address beneficiary; 15 | } 16 | -------------------------------------------------------------------------------- /contracts/structs/JBFundAccessConstraints.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBPaymentTerminal.sol'; 5 | 6 | /** 7 | @member terminal The terminal within which the distribution limit and the overflow allowance applies. 8 | @member token The token for which the fund access constraints apply. 9 | @member distributionLimit The amount of the distribution limit, as a fixed point number with the same number of decimals as the terminal within which the limit applies. 10 | @member distributionLimitCurrency The currency of the distribution limit. 11 | @member overflowAllowance The amount of the allowance, as a fixed point number with the same number of decimals as the terminal within which the allowance applies. 12 | @member overflowAllowanceCurrency The currency of the overflow allowance. 13 | */ 14 | struct JBFundAccessConstraints { 15 | IJBPaymentTerminal terminal; 16 | address token; 17 | uint256 distributionLimit; 18 | uint256 distributionLimitCurrency; 19 | uint256 overflowAllowance; 20 | uint256 overflowAllowanceCurrency; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/structs/JBFundingCycle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBFundingCycleBallot.sol'; 5 | 6 | /** 7 | @member number The funding cycle number for the cycle's project. Each funding cycle has a number that is an increment of the cycle that directly preceded it. Each project's first funding cycle has a number of 1. 8 | @member configuration The timestamp when the parameters for this funding cycle were configured. This value will stay the same for subsequent funding cycles that roll over from an originally configured cycle. 9 | @member basedOn The `configuration` of the funding cycle that was active when this cycle was created. 10 | @member start The timestamp marking the moment from which the funding cycle is considered active. It is a unix timestamp measured in seconds. 11 | @member duration The number of seconds the funding cycle lasts for, after which a new funding cycle will start. A duration of 0 means that the funding cycle will stay active until the project owner explicitly issues a reconfiguration, at which point a new funding cycle will immediately start with the updated properties. If the duration is greater than 0, a project owner cannot make changes to a funding cycle's parameters while it is active – any proposed changes will apply to the subsequent cycle. If no changes are proposed, a funding cycle rolls over to another one with the same properties but new `start` timestamp and a discounted `weight`. 12 | @member weight A fixed point number with 18 decimals that contracts can use to base arbitrary calculations on. For example, payment terminals can use this to determine how many tokens should be minted when a payment is received. 13 | @member discountRate A percent by how much the `weight` of the subsequent funding cycle should be reduced, if the project owner hasn't configured the subsequent funding cycle with an explicit `weight`. If it's 0, each funding cycle will have equal weight. If the number is 90%, the next funding cycle will have a 10% smaller weight. This weight is out of `JBConstants.MAX_DISCOUNT_RATE`. 14 | @member ballot An address of a contract that says whether a proposed reconfiguration should be accepted or rejected. It can be used to create rules around how a project owner can change funding cycle parameters over time. 15 | @member metadata Extra data that can be associated with a funding cycle. 16 | */ 17 | struct JBFundingCycle { 18 | uint256 number; 19 | uint256 configuration; 20 | uint256 basedOn; 21 | uint256 start; 22 | uint256 duration; 23 | uint256 weight; 24 | uint256 discountRate; 25 | IJBFundingCycleBallot ballot; 26 | uint256 metadata; 27 | } 28 | -------------------------------------------------------------------------------- /contracts/structs/JBFundingCycleData.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBFundingCycleBallot.sol'; 5 | 6 | /** 7 | @member duration The number of seconds the funding cycle lasts for, after which a new funding cycle will start. A duration of 0 means that the funding cycle will stay active until the project owner explicitly issues a reconfiguration, at which point a new funding cycle will immediately start with the updated properties. If the duration is greater than 0, a project owner cannot make changes to a funding cycle's parameters while it is active – any proposed changes will apply to the subsequent cycle. If no changes are proposed, a funding cycle rolls over to another one with the same properties but new `start` timestamp and a discounted `weight`. 8 | @member weight A fixed point number with 18 decimals that contracts can use to base arbitrary calculations on. For example, payment terminals can use this to determine how many tokens should be minted when a payment is received. 9 | @member discountRate A percent by how much the `weight` of the subsequent funding cycle should be reduced, if the project owner hasn't configured the subsequent funding cycle with an explicit `weight`. If it's 0, each funding cycle will have equal weight. If the number is 90%, the next funding cycle will have a 10% smaller weight. This weight is out of `JBConstants.MAX_DISCOUNT_RATE`. 10 | @member ballot An address of a contract that says whether a proposed reconfiguration should be accepted or rejected. It can be used to create rules around how a project owner can change funding cycle parameters over time. 11 | */ 12 | struct JBFundingCycleData { 13 | uint256 duration; 14 | uint256 weight; 15 | uint256 discountRate; 16 | IJBFundingCycleBallot ballot; 17 | } 18 | -------------------------------------------------------------------------------- /contracts/structs/JBFundingCycleMetadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './JBGlobalFundingCycleMetadata.sol'; 5 | import './../interfaces/IJBFundingCycleDataSource.sol'; 6 | 7 | /** 8 | @member global Data used globally in non-migratable ecosystem contracts. 9 | @member reservedRate The reserved rate of the funding cycle. This number is a percentage calculated out of `JBConstants.MAX_RESERVED_RATE`. 10 | @member redemptionRate The redemption rate of the funding cycle. This number is a percentage calculated out of `JBConstants.MAX_REDEMPTION_RATE`. 11 | @member ballotRedemptionRate The redemption rate to use during an active ballot of the funding cycle. This number is a percentage calculated out of `JBConstants.MAX_REDEMPTION_RATE`. 12 | @member pausePay A flag indicating if the pay functionality should be paused during the funding cycle. 13 | @member pauseDistributions A flag indicating if the distribute functionality should be paused during the funding cycle. 14 | @member pauseRedeem A flag indicating if the redeem functionality should be paused during the funding cycle. 15 | @member pauseBurn A flag indicating if the burn functionality should be paused during the funding cycle. 16 | @member allowMinting A flag indicating if the mint functionality should be allowed during the funding cycle. 17 | @member allowChangeToken A flag indicating if changing tokens should be allowed during this funding cycle. 18 | @member allowTerminalMigration A flag indicating if migrating terminals should be allowed during this funding cycle. 19 | @member allowControllerMigration A flag indicating if migrating controllers should be allowed during this funding cycle. 20 | @member holdFees A flag indicating if fees should be held during this funding cycle. 21 | @member useTotalOverflowForRedemptions A flag indicating if redemptions should use the project's balance held in all terminals instead of the project's local terminal balance from which the redemption is being fulfilled. 22 | @member useDataSourceForPay A flag indicating if the data source should be used for pay transactions during this funding cycle. 23 | @member useDataSourceForRedeem A flag indicating if the data source should be used for redeem transactions during this funding cycle. 24 | @member dataSource The data source to use during this funding cycle. 25 | */ 26 | struct JBFundingCycleMetadata { 27 | JBGlobalFundingCycleMetadata global; 28 | uint256 reservedRate; 29 | uint256 redemptionRate; 30 | uint256 ballotRedemptionRate; 31 | bool pausePay; 32 | bool pauseDistributions; 33 | bool pauseRedeem; 34 | bool pauseBurn; 35 | bool allowMinting; 36 | bool allowChangeToken; 37 | bool allowTerminalMigration; 38 | bool allowControllerMigration; 39 | bool holdFees; 40 | bool useTotalOverflowForRedemptions; 41 | bool useDataSourceForPay; 42 | bool useDataSourceForRedeem; 43 | address dataSource; 44 | } 45 | -------------------------------------------------------------------------------- /contracts/structs/JBGlobalFundingCycleMetadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBFundingCycleDataSource.sol'; 5 | 6 | /** 7 | @member allowSetTerminals A flag indicating if setting terminals should be allowed during this funding cycle. 8 | @member allowSetController A flag indicating if setting a new controller should be allowed during this funding cycle. 9 | */ 10 | struct JBGlobalFundingCycleMetadata { 11 | bool allowSetTerminals; 12 | bool allowSetController; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/structs/JBGroupedSplits.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './JBSplit.sol'; 5 | 6 | /** 7 | @member group The group indentifier. 8 | @member splits The splits to associate with the group. 9 | */ 10 | struct JBGroupedSplits { 11 | uint256 group; 12 | JBSplit[] splits; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/structs/JBOperatorData.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | @member operator The address of the operator. 6 | @member domain The domain within which the operator is being given permissions. A domain of 0 is a wildcard domain, which gives an operator access to all domains. 7 | @member permissionIndexes The indexes of the permissions the operator is being given. 8 | */ 9 | struct JBOperatorData { 10 | address operator; 11 | uint256 domain; 12 | uint256[] permissionIndexes; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/structs/JBPayParamsData.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBPaymentTerminal.sol'; 5 | import './JBTokenAmount.sol'; 6 | 7 | /** 8 | @member terminal The terminal that is facilitating the payment. 9 | @member payer The address from which the payment originated. 10 | @member amount The amount of the payment. Includes the token being paid, the value, the number of decimals included, and the currency of the amount. 11 | @member projectId The ID of the project being paid. 12 | @member currentFundingCycleConfiguration The configuration of the funding cycle during which the payment is being made. 13 | @member beneficiary The specified address that should be the beneficiary of anything that results from the payment. 14 | @member weight The weight of the funding cycle during which the payment is being made. 15 | @member reservedRate The reserved rate of the funding cycle during which the payment is being made. 16 | @member memo The memo that was sent alongside the payment. 17 | @member metadata Extra data provided by the payer. 18 | */ 19 | struct JBPayParamsData { 20 | IJBPaymentTerminal terminal; 21 | address payer; 22 | JBTokenAmount amount; 23 | uint256 projectId; 24 | uint256 currentFundingCycleConfiguration; 25 | address beneficiary; 26 | uint256 weight; 27 | uint256 reservedRate; 28 | string memo; 29 | bytes metadata; 30 | } 31 | -------------------------------------------------------------------------------- /contracts/structs/JBProjectMetadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | @member content The metadata content. 6 | @member domain The domain within which the metadata applies. 7 | */ 8 | struct JBProjectMetadata { 9 | string content; 10 | uint256 domain; 11 | } 12 | -------------------------------------------------------------------------------- /contracts/structs/JBRedeemParamsData.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBPaymentTerminal.sol'; 5 | import './JBTokenAmount.sol'; 6 | 7 | /** 8 | @member terminal The terminal that is facilitating the redemption. 9 | @member holder The holder of the tokens being redeemed. 10 | @member projectId The ID of the project whos tokens are being redeemed. 11 | @member currentFundingCycleConfiguration The configuration of the funding cycle during which the redemption is being made. 12 | @member tokenCount The proposed number of tokens being redeemed, as a fixed point number with 18 decimals. 13 | @member totalSupply The total supply of tokens used in the calculation, as a fixed point number with 18 decimals. 14 | @member overflow The amount of overflow used in the reclaim amount calculation. 15 | @member reclaimAmount The amount that should be reclaimed by the redeemer using the protocol's standard bonding curve redemption formula. Includes the token being reclaimed, the reclaim value, the number of decimals included, and the currency of the reclaim amount. 16 | @member useTotalOverflow If overflow across all of a project's terminals is being used when making redemptions. 17 | @member redemptionRate The redemption rate of the funding cycle during which the redemption is being made. 18 | @member ballotRedemptionRate The ballot redemption rate of the funding cycle during which the redemption is being made. 19 | @member memo The proposed memo that is being emitted alongside the redemption. 20 | @member metadata Extra data provided by the redeemer. 21 | */ 22 | struct JBRedeemParamsData { 23 | IJBPaymentTerminal terminal; 24 | address holder; 25 | uint256 projectId; 26 | uint256 currentFundingCycleConfiguration; 27 | uint256 tokenCount; 28 | uint256 totalSupply; 29 | uint256 overflow; 30 | JBTokenAmount reclaimAmount; 31 | bool useTotalOverflow; 32 | uint256 redemptionRate; 33 | uint256 ballotRedemptionRate; 34 | string memo; 35 | bytes metadata; 36 | } 37 | -------------------------------------------------------------------------------- /contracts/structs/JBSplit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './../interfaces/IJBSplitAllocator.sol'; 5 | 6 | /** 7 | @member preferClaimed A flag that only has effect if a projectId is also specified, and the project has a token contract attached. If so, this flag indicates if the tokens that result from making a payment to the project should be delivered claimed into the beneficiary's wallet, or unclaimed to save gas. 8 | @member preferAddToBalance A flag indicating if a distribution to a project should prefer triggering it's addToBalance function instead of its pay function. 9 | @member percent The percent of the whole group that this split occupies. This number is out of `JBConstants.SPLITS_TOTAL_PERCENT`. 10 | @member projectId The ID of a project. If an allocator is not set but a projectId is set, funds will be sent to the protocol treasury belonging to the project who's ID is specified. Resulting tokens will be routed to the beneficiary with the claimed token preference respected. 11 | @member beneficiary An address. The role the of the beneficary depends on whether or not projectId is specified, and whether or not an allocator is specified. If allocator is set, the beneficiary will be forwarded to the allocator for it to use. If allocator is not set but projectId is set, the beneficiary is the address to which the project's tokens will be sent that result from a payment to it. If neither allocator or projectId are set, the beneficiary is where the funds from the split will be sent. 12 | @member lockedUntil Specifies if the split should be unchangeable until the specified time, with the exception of extending the locked period. 13 | @member allocator If an allocator is specified, funds will be sent to the allocator contract along with all properties of this split. 14 | */ 15 | struct JBSplit { 16 | bool preferClaimed; 17 | bool preferAddToBalance; 18 | uint256 percent; 19 | uint256 projectId; 20 | address payable beneficiary; 21 | uint256 lockedUntil; 22 | IJBSplitAllocator allocator; 23 | } 24 | -------------------------------------------------------------------------------- /contracts/structs/JBSplitAllocationData.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import './JBSplit.sol'; 5 | 6 | /** 7 | @member token The token being sent to the split allocator. 8 | @member amount The amount being sent to the split allocator, as a fixed point number. 9 | @member decimals The number of decimals in the amount. 10 | @member projectId The project to which the split belongs. 11 | @member group The group to which the split belongs. 12 | @member split The split that caused the allocation. 13 | */ 14 | struct JBSplitAllocationData { 15 | address token; 16 | uint256 amount; 17 | uint256 decimals; 18 | uint256 projectId; 19 | uint256 group; 20 | JBSplit split; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/structs/JBTokenAmount.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /* 5 | @member token The token the payment was made in. 6 | @member value The amount of tokens that was paid, as a fixed point number. 7 | @member decimals The number of decimals included in the value fixed point number. 8 | @member currency The expected currency of the value. 9 | **/ 10 | struct JBTokenAmount { 11 | address token; 12 | uint256 value; 13 | uint256 decimals; 14 | uint256 currency; 15 | } 16 | -------------------------------------------------------------------------------- /contracts/system_tests/TestLaunchProject.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import './helpers/TestBaseWorkflow.sol'; 5 | 6 | contract TestLaunchProject is TestBaseWorkflow { 7 | JBController controller; 8 | JBProjectMetadata _projectMetadata; 9 | JBFundingCycleData _data; 10 | JBFundingCycleMetadata _metadata; 11 | JBGroupedSplits[] _groupedSplits; // Default empty 12 | JBFundAccessConstraints[] _fundAccessConstraints; // Default empty 13 | IJBPaymentTerminal[] _terminals; // Default empty 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | 18 | controller = jbController(); 19 | 20 | _projectMetadata = JBProjectMetadata({content: 'myIPFSHash', domain: 1}); 21 | 22 | _data = JBFundingCycleData({ 23 | duration: 14, 24 | weight: 1000 * 10**18, 25 | discountRate: 450000000, 26 | ballot: IJBFundingCycleBallot(address(0)) 27 | }); 28 | 29 | _metadata = JBFundingCycleMetadata({ 30 | global: JBGlobalFundingCycleMetadata({allowSetTerminals: false, allowSetController: false}), 31 | reservedRate: 5000, //50% 32 | redemptionRate: 5000, //50% 33 | ballotRedemptionRate: 0, 34 | pausePay: false, 35 | pauseDistributions: false, 36 | pauseRedeem: false, 37 | pauseBurn: false, 38 | allowMinting: false, 39 | allowChangeToken: false, 40 | allowTerminalMigration: false, 41 | allowControllerMigration: false, 42 | holdFees: false, 43 | useTotalOverflowForRedemptions: false, 44 | useDataSourceForPay: false, 45 | useDataSourceForRedeem: false, 46 | dataSource: address(0) 47 | }); 48 | } 49 | 50 | function testLaunchProject() public { 51 | uint256 projectId = controller.launchProjectFor( 52 | msg.sender, 53 | _projectMetadata, 54 | _data, 55 | _metadata, 56 | block.timestamp, 57 | _groupedSplits, 58 | _fundAccessConstraints, 59 | _terminals, 60 | '' 61 | ); 62 | 63 | JBFundingCycle memory fundingCycle = jbFundingCycleStore().currentOf(projectId); //, latestConfig); 64 | 65 | assertEq(fundingCycle.number, 1); 66 | assertEq(fundingCycle.weight, 1000 * 10**18); 67 | } 68 | 69 | function testLaunchProjectFuzzWeight(uint256 WEIGHT) public { 70 | _data = JBFundingCycleData({ 71 | duration: 14, 72 | weight: WEIGHT, 73 | discountRate: 450000000, 74 | ballot: IJBFundingCycleBallot(address(0)) 75 | }); 76 | 77 | uint256 projectId; 78 | 79 | // expectRevert on the next call if weight overflowing 80 | if (WEIGHT > type(uint88).max) { 81 | evm.expectRevert(abi.encodeWithSignature('INVALID_WEIGHT()')); 82 | 83 | projectId = controller.launchProjectFor( 84 | msg.sender, 85 | _projectMetadata, 86 | _data, 87 | _metadata, 88 | block.timestamp, 89 | _groupedSplits, 90 | _fundAccessConstraints, 91 | _terminals, 92 | '' 93 | ); 94 | } else { 95 | projectId = controller.launchProjectFor( 96 | msg.sender, 97 | _projectMetadata, 98 | _data, 99 | _metadata, 100 | block.timestamp, 101 | _groupedSplits, 102 | _fundAccessConstraints, 103 | _terminals, 104 | '' 105 | ); 106 | 107 | JBFundingCycle memory fundingCycle = jbFundingCycleStore().currentOf(projectId); //, latestConfig); 108 | 109 | assertEq(fundingCycle.number, 1); 110 | assertEq(fundingCycle.weight, WEIGHT); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /contracts/system_tests/helpers/AccessJBLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import '../../libraries/JBCurrencies.sol'; 5 | import '../../libraries/JBConstants.sol'; 6 | import '../../libraries/JBTokens.sol'; 7 | 8 | contract AccessJBLib { 9 | function ETH() external pure returns (uint256) { 10 | return JBCurrencies.ETH; 11 | } 12 | 13 | function USD() external pure returns (uint256) { 14 | return JBCurrencies.USD; 15 | } 16 | 17 | function ETHToken() external pure returns (address) { 18 | return JBTokens.ETH; 19 | } 20 | 21 | function MAX_FEE() external pure returns (uint256) { 22 | return JBConstants.MAX_FEE; 23 | } 24 | 25 | function MAX_RESERVED_RATE() external pure returns (uint256) { 26 | return JBConstants.MAX_RESERVED_RATE; 27 | } 28 | 29 | function MAX_REDEMPTION_RATE() external pure returns (uint256) { 30 | return JBConstants.MAX_REDEMPTION_RATE; 31 | } 32 | 33 | function MAX_DISCOUNT_RATE() external pure returns (uint256) { 34 | return JBConstants.MAX_DISCOUNT_RATE; 35 | } 36 | 37 | function SPLITS_TOTAL_PERCENT() external pure returns (uint256) { 38 | return JBConstants.SPLITS_TOTAL_PERCENT; 39 | } 40 | 41 | function MAX_FEE_DISCOUNT() external pure returns (uint256) { 42 | return JBConstants.MAX_FEE_DISCOUNT; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/system_tests/helpers/hevm.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.6; 2 | 3 | /// @dev the cheat-code contract lives at 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D 4 | interface Hevm { 5 | // Set block.timestamp (newTimestamp) 6 | function warp(uint256) external; 7 | 8 | // Set block.height (newHeight) 9 | function roll(uint256) external; 10 | 11 | // Set block.basefee (newBasefee) 12 | function fee(uint256) external; 13 | 14 | // Loads a storage slot from an address (who, slot) 15 | function load(address, bytes32) external returns (bytes32); 16 | 17 | // Stores a value to an address' storage slot, (who, slot, value) 18 | function store( 19 | address, 20 | bytes32, 21 | bytes32 22 | ) external; 23 | 24 | // Signs data, (privateKey, digest) => (v, r, s) 25 | function sign(uint256, bytes32) 26 | external 27 | returns ( 28 | uint8, 29 | bytes32, 30 | bytes32 31 | ); 32 | 33 | // Gets address for a given private key, (privateKey) => (address) 34 | function addr(uint256) external returns (address); 35 | 36 | // Performs a foreign function call via terminal, (stringInputs) => (result) 37 | function ffi(string[] calldata) external returns (bytes memory); 38 | 39 | // Sets the *next* call's msg.sender to be the input address 40 | function prank(address) external; 41 | 42 | // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called 43 | function startPrank(address) external; 44 | 45 | // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input 46 | function prank(address, address) external; 47 | 48 | // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input 49 | function startPrank(address, address) external; 50 | 51 | // Resets subsequent calls' msg.sender to be `address(this)` 52 | function stopPrank() external; 53 | 54 | // Sets an address' balance, (who, newBalance) 55 | function deal(address, uint256) external; 56 | 57 | // Sets an address' code, (who, newCode) 58 | function etch(address, bytes calldata) external; 59 | 60 | // Expects an error on next call 61 | function expectRevert(bytes calldata) external; 62 | 63 | function expectRevert(bytes4) external; 64 | 65 | // Record all storage reads and writes 66 | function record() external; 67 | 68 | // Gets all accessed reads and write slot from a recording session, for a given address 69 | function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); 70 | 71 | // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). 72 | // Call this function, then emit an event, then call a function. Internally after the call, we check if 73 | // logs were emitted in the expected order with the expected topics and data (as specified by the booleans) 74 | function expectEmit( 75 | bool, 76 | bool, 77 | bool, 78 | bool 79 | ) external; 80 | 81 | // Mocks a call to an address, returning specified data. 82 | // Calldata can either be strict or a partial match, e.g. if you only 83 | // pass a Solidity selector to the expected calldata, then the entire Solidity 84 | // function will be mocked. 85 | function mockCall( 86 | address, 87 | bytes calldata, 88 | bytes calldata 89 | ) external; 90 | 91 | // Clears all mocked calls 92 | function clearMockedCalls() external; 93 | 94 | // Expect a call to an address with the specified calldata. 95 | // Calldata can either be strict or a partial match 96 | function expectCall(address, bytes calldata) external; 97 | 98 | // Gets the code from an artifact file. Takes in the relative path to the json file 99 | function getCode(string calldata) external returns (bytes memory); 100 | 101 | // Labels an address in call traces 102 | function label(address, string calldata) external; 103 | 104 | // If the condition is false, discard this run's fuzz inputs and generate new ones 105 | function assume(bool) external; 106 | } 107 | -------------------------------------------------------------------------------- /contracts/system_tests/mock/MockPriceFeed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.6; 3 | 4 | import '../../interfaces/IJBPriceFeed.sol'; 5 | 6 | contract MockPriceFeed is IJBPriceFeed { 7 | uint256 public fakePrice; 8 | 9 | constructor(uint256 _fakePrice) { 10 | fakePrice = _fakePrice; 11 | } 12 | 13 | function currentPrice(uint256 _decimals) external view override returns (uint256 _quote) { 14 | return (fakePrice * _decimals); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /deploy/3.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat'); 2 | 3 | /** 4 | * Deploys a new SplitsPayerDeployer that deploys an updated SplitsPayer. This will be executed as a stand-alone procedure with no dependencies. 5 | * 6 | * Example usage: 7 | * 8 | * npx hardhat deploy --network rinkeby --tag 3 9 | */ 10 | module.exports = async ({ deployments, getChainId }) => { 11 | console.log("Deploying 3"); 12 | 13 | const { deploy } = deployments; 14 | const [deployer] = await ethers.getSigners(); 15 | 16 | let chainId = await getChainId(); 17 | let baseDeployArgs = { 18 | from: deployer.address, 19 | log: true 20 | }; 21 | 22 | console.log({ deployer: deployer.address, chain: chainId }); 23 | 24 | // Deploy a JBETHERC20SplitsPayerDeployer contract. 25 | await deploy('JBETHERC20SplitsPayerDeployer', { 26 | ...baseDeployArgs, 27 | skipIfAlreadyDeployed: false, 28 | contract: "contracts/JBETHERC20SplitsPayerDeployer.sol:JBETHERC20SplitsPayerDeployer", 29 | args: [], 30 | }); 31 | 32 | console.log('Done'); 33 | }; 34 | 35 | module.exports.tags = ['3']; -------------------------------------------------------------------------------- /deployments/goerli/.chainId: -------------------------------------------------------------------------------- 1 | 5 -------------------------------------------------------------------------------- /deployments/goerli/JBCurrencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0x5C9f2fd6cE9E600dd9c4cA4cd67e777CbaB6353b", 3 | "abi": [ 4 | { 5 | "inputs": [], 6 | "name": "ETH", 7 | "outputs": [ 8 | { 9 | "internalType": "uint256", 10 | "name": "", 11 | "type": "uint256" 12 | } 13 | ], 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "inputs": [], 19 | "name": "USD", 20 | "outputs": [ 21 | { 22 | "internalType": "uint256", 23 | "name": "", 24 | "type": "uint256" 25 | } 26 | ], 27 | "stateMutability": "view", 28 | "type": "function" 29 | } 30 | ], 31 | "transactionHash": "0xc2f2cfbf4a76dcc08629ad7d974bc86c4539c0a1bec2ba3fc2566f7978e445d3", 32 | "receipt": { 33 | "to": null, 34 | "from": "0x3443d0a6956e7E0A13Cd1c54F6bEf24B0d54f420", 35 | "contractAddress": "0x5C9f2fd6cE9E600dd9c4cA4cd67e777CbaB6353b", 36 | "transactionIndex": 8, 37 | "gasUsed": "86477", 38 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 39 | "blockHash": "0x46870e9ce10a136ae1b47bfd8e0204d22723e3647213b08163cc3beeef063a9c", 40 | "transactionHash": "0xc2f2cfbf4a76dcc08629ad7d974bc86c4539c0a1bec2ba3fc2566f7978e445d3", 41 | "logs": [], 42 | "blockNumber": 7739767, 43 | "cumulativeGasUsed": "6024516", 44 | "status": 1, 45 | "byzantium": true 46 | }, 47 | "args": [], 48 | "numDeployments": 1, 49 | "solcInputHash": "e9bf28e6b06ac987169c3c7105c7ab19", 50 | "metadata": "{\"compiler\":{\"version\":\"0.8.6+commit.11564f7e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"ETH\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"USD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/libraries/JBCurrencies.sol\":\"JBCurrencies\"},\"evmVersion\":\"berlin\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/libraries/JBCurrencies.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\nlibrary JBCurrencies {\\n uint256 public constant ETH = 1;\\n uint256 public constant USD = 2;\\n}\\n\",\"keccak256\":\"0x7e417ff25c173608ee4fe6d9fc3dcd5e1458c78c889af12bac47b1189a436076\",\"license\":\"MIT\"}},\"version\":1}", 51 | "bytecode": "0x6098610038600b82828239805160001a607314602b57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631bf6c21b1460425780638322fff214605b575b600080fd5b6049600281565b60405190815260200160405180910390f35b604960018156fea2646970667358221220b9b06ce520d10723df290a54103442e026b216b2288289ba9d3fedf9c152e0dc64736f6c63430008060033", 52 | "deployedBytecode": "0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631bf6c21b1460425780638322fff214605b575b600080fd5b6049600281565b60405190815260200160405180910390f35b604960018156fea2646970667358221220b9b06ce520d10723df290a54103442e026b216b2288289ba9d3fedf9c152e0dc64736f6c63430008060033", 53 | "devdoc": { 54 | "kind": "dev", 55 | "methods": {}, 56 | "version": 1 57 | }, 58 | "userdoc": { 59 | "kind": "user", 60 | "methods": {}, 61 | "version": 1 62 | }, 63 | "storageLayout": { 64 | "storage": [], 65 | "types": null 66 | } 67 | } -------------------------------------------------------------------------------- /deployments/mainnet/.chainId: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /deployments/mainnet/JBCurrencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0x7F6f2bb90256eAD1189a16A86efCdC0122141c01", 3 | "abi": [ 4 | { 5 | "inputs": [], 6 | "name": "ETH", 7 | "outputs": [ 8 | { 9 | "internalType": "uint256", 10 | "name": "", 11 | "type": "uint256" 12 | } 13 | ], 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "inputs": [], 19 | "name": "USD", 20 | "outputs": [ 21 | { 22 | "internalType": "uint256", 23 | "name": "", 24 | "type": "uint256" 25 | } 26 | ], 27 | "stateMutability": "view", 28 | "type": "function" 29 | } 30 | ], 31 | "transactionHash": "0x76da5c4cfaba9b79b005d4536875611af446d4105d0548af2eae34c42abaf2ca", 32 | "receipt": { 33 | "to": null, 34 | "from": "0xE9bE6df23C7f9CaBa3005DA2fa2d8714d340D0aF", 35 | "contractAddress": "0x7F6f2bb90256eAD1189a16A86efCdC0122141c01", 36 | "transactionIndex": 33, 37 | "gasUsed": "86477", 38 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 39 | "blockHash": "0x4282d6c5d2e850c1b8052ebbbe74e23f8c0095ca7f44f6e9a7ab6b16e0a7ec6a", 40 | "transactionHash": "0x76da5c4cfaba9b79b005d4536875611af446d4105d0548af2eae34c42abaf2ca", 41 | "logs": [], 42 | "blockNumber": 14730703, 43 | "cumulativeGasUsed": "1481257", 44 | "status": 1, 45 | "byzantium": true 46 | }, 47 | "args": [], 48 | "numDeployments": 1, 49 | "solcInputHash": "87acdcf5deeaa43ae3ecf62f45455645", 50 | "metadata": "{\"compiler\":{\"version\":\"0.8.6+commit.11564f7e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"ETH\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"USD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/libraries/JBCurrencies.sol\":\"JBCurrencies\"},\"evmVersion\":\"berlin\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/libraries/JBCurrencies.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.6;\\n\\nlibrary JBCurrencies {\\n uint256 public constant ETH = 1;\\n uint256 public constant USD = 2;\\n}\\n\",\"keccak256\":\"0x3077e365b09d45e3a82d6710bfda7323a0366c3cafc318ea9dc442a1a93036d7\",\"license\":\"MIT\"}},\"version\":1}", 51 | "bytecode": "0x6098610038600b82828239805160001a607314602b57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631bf6c21b1460425780638322fff214605b575b600080fd5b6049600281565b60405190815260200160405180910390f35b604960018156fea2646970667358221220b81a28cbe265f00dcf443051583017dc7b011f520665d91d3025c1a3e24ea31c64736f6c63430008060033", 52 | "deployedBytecode": "0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631bf6c21b1460425780638322fff214605b575b600080fd5b6049600281565b60405190815260200160405180910390f35b604960018156fea2646970667358221220b81a28cbe265f00dcf443051583017dc7b011f520665d91d3025c1a3e24ea31c64736f6c63430008060033", 53 | "devdoc": { 54 | "kind": "dev", 55 | "methods": {}, 56 | "version": 1 57 | }, 58 | "userdoc": { 59 | "kind": "user", 60 | "methods": {}, 61 | "version": 1 62 | }, 63 | "storageLayout": { 64 | "storage": [], 65 | "types": null 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /deployments/rinkeby/.chainId: -------------------------------------------------------------------------------- 1 | 4 -------------------------------------------------------------------------------- /deployments/rinkeby/JBCurrencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0x1bA7138f8bb6D59A74A883a0E3104D1b1B698aD4", 3 | "abi": [ 4 | { 5 | "inputs": [], 6 | "name": "ETH", 7 | "outputs": [ 8 | { 9 | "internalType": "uint256", 10 | "name": "", 11 | "type": "uint256" 12 | } 13 | ], 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "inputs": [], 19 | "name": "USD", 20 | "outputs": [ 21 | { 22 | "internalType": "uint256", 23 | "name": "", 24 | "type": "uint256" 25 | } 26 | ], 27 | "stateMutability": "view", 28 | "type": "function" 29 | } 30 | ], 31 | "transactionHash": "0xdb75d32c4038641fa5db8badee8de582c0ddbf31b5d021ca8ef24092873a8ba1", 32 | "receipt": { 33 | "to": null, 34 | "from": "0xE9bE6df23C7f9CaBa3005DA2fa2d8714d340D0aF", 35 | "contractAddress": "0x1bA7138f8bb6D59A74A883a0E3104D1b1B698aD4", 36 | "transactionIndex": 1, 37 | "gasUsed": "86477", 38 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 39 | "blockHash": "0xe9ebe296e51ee76b76cabc4ac51803a4126792d24ed99a01548fb2bae6ceb01f", 40 | "transactionHash": "0xdb75d32c4038641fa5db8badee8de582c0ddbf31b5d021ca8ef24092873a8ba1", 41 | "logs": [], 42 | "blockNumber": 10635491, 43 | "cumulativeGasUsed": "120046", 44 | "status": 1, 45 | "byzantium": true 46 | }, 47 | "args": [], 48 | "numDeployments": 1, 49 | "solcInputHash": "87acdcf5deeaa43ae3ecf62f45455645", 50 | "metadata": "{\"compiler\":{\"version\":\"0.8.6+commit.11564f7e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"ETH\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"USD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/libraries/JBCurrencies.sol\":\"JBCurrencies\"},\"evmVersion\":\"berlin\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/libraries/JBCurrencies.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.6;\\n\\nlibrary JBCurrencies {\\n uint256 public constant ETH = 1;\\n uint256 public constant USD = 2;\\n}\\n\",\"keccak256\":\"0x3077e365b09d45e3a82d6710bfda7323a0366c3cafc318ea9dc442a1a93036d7\",\"license\":\"MIT\"}},\"version\":1}", 51 | "bytecode": "0x6098610038600b82828239805160001a607314602b57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631bf6c21b1460425780638322fff214605b575b600080fd5b6049600281565b60405190815260200160405180910390f35b604960018156fea2646970667358221220b81a28cbe265f00dcf443051583017dc7b011f520665d91d3025c1a3e24ea31c64736f6c63430008060033", 52 | "deployedBytecode": "0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631bf6c21b1460425780638322fff214605b575b600080fd5b6049600281565b60405190815260200160405180910390f35b604960018156fea2646970667358221220b81a28cbe265f00dcf443051583017dc7b011f520665d91d3025c1a3e24ea31c64736f6c63430008060033", 53 | "devdoc": { 54 | "kind": "dev", 55 | "methods": {}, 56 | "version": 1 57 | }, 58 | "userdoc": { 59 | "kind": "user", 60 | "methods": {}, 61 | "version": 1 62 | }, 63 | "storageLayout": { 64 | "storage": [], 65 | "types": null 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | src = 'contracts' 3 | test = 'test' 4 | out = 'out' 5 | libs = ['lib'] 6 | remappings = [ 7 | '@juicebox=contracts/', 8 | '@chainlink=lib/chainlink/', 9 | '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', 10 | '@paulrberg/contracts/math/=lib/prb-math/contracts', 11 | 'prb-math=lib/prb-math/', 12 | 'ds-test/=lib/ds-test/src/', 13 | 'forge-std/=lib/forge-std/src/', 14 | ] 15 | libraries = [] 16 | cache = true 17 | force = false 18 | evm_version = 'london' 19 | gas_reports = ['*'] 20 | auto_detect_solc = true 21 | optimizer = true 22 | optimizer_runs = 200 23 | verbosity = 0 24 | ignored_error_codes = [] 25 | fuzz_runs = 1024 26 | ffi = false 27 | sender = '0x00a329c0648769a73afac7f9381e08fb43dbea72' 28 | tx_origin = '0x00a329c0648769a73afac7f9381e08fb43dbea72' 29 | initial_balance = '0xffffffffffffffffffffffff' 30 | block_number = 14126430 31 | gas_limit = 9223372036854775807 32 | gas_price = 0 33 | block_base_fee_per_gas = 0 34 | block_coinbase = '0x0000000000000000000000000000000000000000' 35 | block_timestamp = 1643802347 36 | block_difficulty = 0 37 | 38 | -------------------------------------------------------------------------------- /lib/ds-test/Makefile: -------------------------------------------------------------------------------- 1 | all:; dapp build 2 | 3 | test: 4 | -dapp --use solc:0.4.23 build 5 | -dapp --use solc:0.4.26 build 6 | -dapp --use solc:0.5.17 build 7 | -dapp --use solc:0.6.12 build 8 | -dapp --use solc:0.7.5 build 9 | 10 | demo: 11 | DAPP_SRC=demo dapp --use solc:0.7.5 build 12 | -hevm dapp-test --verbose 3 13 | 14 | .PHONY: test demo 15 | -------------------------------------------------------------------------------- /lib/ds-test/default.nix: -------------------------------------------------------------------------------- 1 | { solidityPackage, dappsys }: solidityPackage { 2 | name = "ds-test"; 3 | src = ./src; 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jbx-protocol/contracts-v2", 3 | "bugs": { 4 | "url": "https://github.com/jbx-protocol/juice-contracts-v2/issues" 5 | }, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jbx-protocol/juice-contracts-v2" 9 | }, 10 | "version": "8.0.4", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@chainlink/contracts": "^0.1.6", 14 | "@nomiclabs/hardhat-ethers": "^2.0.2", 15 | "@nomiclabs/hardhat-etherscan": "^2.1.4", 16 | "@nomiclabs/hardhat-waffle": "^2.0.1", 17 | "@openzeppelin/contracts": "^4.5.0-rc.0", 18 | "@paulrberg/contracts": "^3.4.0", 19 | "chai": "^4.3.4", 20 | "dotenv": "^10.0.0", 21 | "esm": "^3.2.25", 22 | "ethereum-waffle": "^3.4.0", 23 | "ethers": "^5.4.6", 24 | "glob": "^7.2.0", 25 | "hardhat": "^2.9.3", 26 | "hardhat-deploy": "^0.9.1", 27 | "hardhat-deploy-ethers": "^0.3.0-beta.10", 28 | "hardhat-gas-reporter": "^1.0.4", 29 | "prettier": "^2.4.0", 30 | "prettier-plugin-solidity": "^1.0.0-beta.19", 31 | "solhint": "^3.3.6", 32 | "solhint-plugin-prettier": "^0.0.5", 33 | "solidity-coverage": "^0.8.0-beta.1" 34 | }, 35 | "scripts": { 36 | "chain": "hardhat node", 37 | "account": "hardhat account", 38 | "test": "mocha './test/**/*.test.js' -r esm --bail", 39 | "coverage": "node --require esm ./node_modules/.bin/hardhat coverage --network hardhat", 40 | "clean": "rimraf ./cache && rimraf ./artifacts", 41 | "compile": "yarn clean && hardhat compile", 42 | "pretty": "prettier --write \"./**/*.{js,jsx,json,sol}\"" 43 | } 44 | } -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @chainlink=lib/chainlink/ 2 | @juicebox=contracts/ 3 | @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 4 | @paulrberg/contracts/math/=lib/prb-math/contracts/ 5 | chainlink/=lib/chainlink/ 6 | ds-test/=lib/ds-test/src/ 7 | forge-std/=lib/forge-std/src/ 8 | openzeppelin-contracts/contracts/=lib/openzeppelin-contracts/contracts/ 9 | prb-math=lib/prb-math/ 10 | prb-math/=lib/prb-math/contracts/ -------------------------------------------------------------------------------- /test/jb_chainlink_price_feed/current_price.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { compilerOutput } from '@chainlink/contracts/abi/v0.6/AggregatorV3Interface.json'; 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | import { BigNumber } from '@ethersproject/bignumber'; 6 | 7 | describe('JBChainlinkV3PriceFeed::currentPrice(...)', function () { 8 | let deployer; 9 | let addrs; 10 | 11 | let aggregatorV3Contract; 12 | 13 | let jbChainlinkPriceFeedFactory; 14 | let jbChainlinkPriceFeed; 15 | let targetDecimals = 18; 16 | 17 | beforeEach(async function () { 18 | [deployer, ...addrs] = await ethers.getSigners(); 19 | 20 | aggregatorV3Contract = await deployMockContract(deployer, compilerOutput.abi); 21 | 22 | jbChainlinkPriceFeedFactory = await ethers.getContractFactory('JBChainlinkV3PriceFeed'); 23 | jbChainlinkPriceFeed = await jbChainlinkPriceFeedFactory.deploy(aggregatorV3Contract.address); 24 | }); 25 | 26 | /** 27 | * Initialiazes mock price feed, adds it to JBPrices, and returns the fetched result. 28 | */ 29 | async function currentPrice(price, decimals) { 30 | await aggregatorV3Contract.mock.latestRoundData.returns(0, price, 0, 0, 0); 31 | await aggregatorV3Contract.mock.decimals.returns(decimals); 32 | return await jbChainlinkPriceFeed.connect(deployer).currentPrice(targetDecimals); 33 | } 34 | 35 | it('Get price no decimals', async function () { 36 | let price = 400; 37 | expect(await currentPrice(price, /*decimals=*/ 0)).to.equal( 38 | ethers.BigNumber.from(price).mul(BigNumber.from(10).pow(targetDecimals)), 39 | ); 40 | }); 41 | 42 | it('Check price less than target decimal', async function () { 43 | let price = 400; 44 | let decimals = targetDecimals - 1; 45 | expect(await currentPrice(price, decimals)).to.equal( 46 | ethers.BigNumber.from(price).mul(BigNumber.from(10).pow(targetDecimals - decimals)), 47 | ); 48 | }); 49 | 50 | it('Check price target decimals', async function () { 51 | let price = 400; 52 | expect(await currentPrice(price, targetDecimals)).to.equal(ethers.BigNumber.from(price)); 53 | }); 54 | 55 | it('Check price more than target decimals', async function () { 56 | let price = 400; 57 | let decimals = targetDecimals + 1; 58 | expect(await currentPrice(price, decimals)).to.equal( 59 | ethers.BigNumber.from(price).div(ethers.BigNumber.from(10).pow(decimals - targetDecimals)), 60 | ); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/jb_controller/prep_for_migration.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 4 | import { impersonateAccount } from '../helpers/utils'; 5 | import errors from '../helpers/errors.json'; 6 | 7 | import JbController from '../../artifacts/contracts/JBController.sol/JBController.json'; 8 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 9 | import jbFundingCycleStore from '../../artifacts/contracts/JBFundingCycleStore.sol/JBFundingCycleStore.json'; 10 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 11 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 12 | import jbSplitsStore from '../../artifacts/contracts/JBSplitsStore.sol/JBSplitsStore.json'; 13 | import jbTokenStore from '../../artifacts/contracts/JBTokenStore.sol/JBTokenStore.json'; 14 | 15 | describe('JBController::prepForMigrationOf(...)', function () { 16 | const PROJECT_ID = 1; 17 | const TOTAL_SUPPLY = 20000; 18 | 19 | async function setup() { 20 | let [deployer, projectOwner, ...addrs] = await ethers.getSigners(); 21 | 22 | let [ 23 | mockJbController, 24 | mockJbDirectory, 25 | mockJbFundingCycleStore, 26 | mockJbOperatorStore, 27 | mockJbProjects, 28 | mockJbSplitsStore, 29 | mockJbTokenStore, 30 | ] = await Promise.all([ 31 | deployMockContract(deployer, JbController.abi), 32 | deployMockContract(deployer, jbDirectory.abi), 33 | deployMockContract(deployer, jbFundingCycleStore.abi), 34 | deployMockContract(deployer, jbOperatoreStore.abi), 35 | deployMockContract(deployer, jbProjects.abi), 36 | deployMockContract(deployer, jbSplitsStore.abi), 37 | deployMockContract(deployer, jbTokenStore.abi), 38 | ]); 39 | 40 | let jbControllerFactory = await ethers.getContractFactory( 41 | 'contracts/JBController.sol:JBController', 42 | ); 43 | let jbController = await jbControllerFactory.deploy( 44 | mockJbOperatorStore.address, 45 | mockJbProjects.address, 46 | mockJbDirectory.address, 47 | mockJbFundingCycleStore.address, 48 | mockJbTokenStore.address, 49 | mockJbSplitsStore.address, 50 | ); 51 | 52 | await mockJbProjects.mock.ownerOf.withArgs(PROJECT_ID).returns(projectOwner.address); 53 | 54 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(mockJbController.address); 55 | 56 | await mockJbTokenStore.mock.totalSupplyOf.withArgs(PROJECT_ID).returns(TOTAL_SUPPLY); 57 | 58 | return { 59 | projectOwner, 60 | addrs, 61 | jbController, 62 | mockJbDirectory, 63 | mockJbTokenStore, 64 | mockJbController, 65 | }; 66 | } 67 | 68 | it(`Should set the processed token tracker as the total supply if caller is not project's current controller`, async function () { 69 | const { jbController } = await setup(); 70 | let controllerSigner = await impersonateAccount(jbController.address); 71 | 72 | const tx = jbController 73 | .connect(controllerSigner) 74 | .prepForMigrationOf(PROJECT_ID, ethers.constants.AddressZero); 75 | 76 | await expect(tx).to.be.not.reverted; 77 | 78 | // reserved token balance should be at 0 if processed token = total supply 79 | expect(await jbController.reservedTokenBalanceOf(PROJECT_ID, 10000)).to.equal(0); 80 | await expect(tx) 81 | .to.emit(jbController, 'PrepMigration') 82 | .withArgs(PROJECT_ID, ethers.constants.AddressZero, controllerSigner.address); 83 | }); 84 | 85 | it(`Can't prep for migration if the caller is the current controller`, async function () { 86 | const { jbController, mockJbController, mockJbDirectory } = await setup(); 87 | let controllerSigner = await impersonateAccount(mockJbController.address); 88 | 89 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(jbController.address); 90 | 91 | await expect( 92 | jbController 93 | .connect(controllerSigner) 94 | .prepForMigrationOf(PROJECT_ID, ethers.constants.AddressZero), 95 | ).to.be.revertedWith(errors.CANT_MIGRATE_TO_CURRENT_CONTROLLER); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/jb_directory/is_terminal_of.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | import { packFundingCycleMetadata } from '../helpers/utils'; 6 | 7 | import jbFundingCycleStore from '../../artifacts/contracts/JBFundingCycleStore.sol/JBFundingCycleStore.json'; 8 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 9 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 10 | import jbTerminal from '../../artifacts/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol/JBPayoutRedemptionPaymentTerminal.json'; 11 | 12 | describe('JBDirectory::isTerminalOf(...)', function () { 13 | const PROJECT_ID = 13; 14 | 15 | let SET_TERMINALS_PERMISSION_INDEX; 16 | 17 | before(async function () { 18 | let jbOperationsFactory = await ethers.getContractFactory('JBOperations'); 19 | let jbOperations = await jbOperationsFactory.deploy(); 20 | 21 | SET_TERMINALS_PERMISSION_INDEX = await jbOperations.SET_TERMINALS(); 22 | }); 23 | 24 | async function setup() { 25 | let [deployer, projectOwner, ...addrs] = await ethers.getSigners(); 26 | 27 | const blockNum = await ethers.provider.getBlockNumber(); 28 | const block = await ethers.provider.getBlock(blockNum); 29 | const timestamp = block.timestamp; 30 | 31 | let mockJbFundingCycleStore = await deployMockContract(deployer, jbFundingCycleStore.abi); 32 | let mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 33 | let mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 34 | 35 | let jbDirectoryFactory = await ethers.getContractFactory('JBDirectory'); 36 | let jbDirectory = await jbDirectoryFactory.deploy( 37 | mockJbOperatorStore.address, 38 | mockJbProjects.address, 39 | mockJbFundingCycleStore.address, 40 | deployer.address, 41 | ); 42 | 43 | let terminal1 = await deployMockContract(projectOwner, jbTerminal.abi); 44 | let terminal2 = await deployMockContract(projectOwner, jbTerminal.abi); 45 | 46 | await mockJbProjects.mock.ownerOf.withArgs(PROJECT_ID).returns(projectOwner.address); 47 | await mockJbOperatorStore.mock.hasPermission 48 | .withArgs( 49 | projectOwner.address, 50 | projectOwner.address, 51 | PROJECT_ID, 52 | SET_TERMINALS_PERMISSION_INDEX, 53 | ) 54 | .returns(true); 55 | 56 | await mockJbFundingCycleStore.mock.currentOf.withArgs(PROJECT_ID).returns({ 57 | number: 1, 58 | configuration: timestamp, 59 | basedOn: timestamp, 60 | start: timestamp, 61 | duration: 0, 62 | weight: 0, 63 | discountRate: 0, 64 | ballot: ethers.constants.AddressZero, 65 | metadata: packFundingCycleMetadata({ global: { allowSetTerminals: true } }), 66 | }); 67 | 68 | // Add a few terminals 69 | await jbDirectory 70 | .connect(projectOwner) 71 | .setTerminalsOf(PROJECT_ID, [terminal1.address, terminal2.address]); 72 | 73 | return { projectOwner, deployer, addrs, jbDirectory, terminal1, terminal2 }; 74 | } 75 | 76 | it('Should return true if the terminal belongs to the project', async function () { 77 | const { projectOwner, jbDirectory, terminal1, terminal2 } = await setup(); 78 | 79 | expect(await jbDirectory.connect(projectOwner).isTerminalOf(PROJECT_ID, terminal1.address)).to 80 | .be.true; 81 | 82 | expect(await jbDirectory.connect(projectOwner).isTerminalOf(PROJECT_ID, terminal2.address)).to 83 | .be.true; 84 | }); 85 | 86 | it(`Should return false if the terminal doesn't belong to the project`, async function () { 87 | const { projectOwner, jbDirectory } = await setup(); 88 | 89 | expect( 90 | await jbDirectory 91 | .connect(projectOwner) 92 | .isTerminalOf(PROJECT_ID, ethers.Wallet.createRandom().address), 93 | ).to.be.false; 94 | }); 95 | 96 | it(`Should return false if the project does not exist`, async function () { 97 | const { projectOwner, jbDirectory } = await setup(); 98 | 99 | expect( 100 | await jbDirectory 101 | .connect(projectOwner) 102 | .isTerminalOf(123, ethers.Wallet.createRandom().address), 103 | ).to.be.false; 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/jb_directory/primary_terminal_of.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | import { packFundingCycleMetadata } from '../helpers/utils'; 6 | 7 | import jbFundingCycleStore from '../../artifacts/contracts/JBFundingCycleStore.sol/JBFundingCycleStore.json'; 8 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 9 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 10 | import jbTerminal from '../../artifacts/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol/JBPayoutRedemptionPaymentTerminal.json'; 11 | 12 | describe('JBDirectory::primaryTerminalOf(...)', function () { 13 | const PROJECT_ID = 13; 14 | 15 | async function setup() { 16 | let [deployer, projectOwner, ...addrs] = await ethers.getSigners(); 17 | 18 | const blockNum = await ethers.provider.getBlockNumber(); 19 | const block = await ethers.provider.getBlock(blockNum); 20 | const timestamp = block.timestamp; 21 | 22 | let mockJbFundingCycleStore = await deployMockContract(deployer, jbFundingCycleStore.abi); 23 | let mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 24 | let mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 25 | 26 | let jbDirectoryFactory = await ethers.getContractFactory('JBDirectory'); 27 | let jbDirectory = await jbDirectoryFactory.deploy( 28 | mockJbOperatorStore.address, 29 | mockJbProjects.address, 30 | mockJbFundingCycleStore.address, 31 | deployer.address, 32 | ); 33 | 34 | let terminal1 = await deployMockContract(projectOwner, jbTerminal.abi); 35 | let terminal2 = await deployMockContract(projectOwner, jbTerminal.abi); 36 | 37 | await mockJbProjects.mock.ownerOf.withArgs(PROJECT_ID).returns(projectOwner.address); 38 | 39 | await mockJbFundingCycleStore.mock.currentOf.withArgs(PROJECT_ID).returns({ 40 | number: 1, 41 | configuration: timestamp, 42 | basedOn: timestamp, 43 | start: timestamp, 44 | duration: 0, 45 | weight: 0, 46 | discountRate: 0, 47 | ballot: ethers.constants.AddressZero, 48 | metadata: packFundingCycleMetadata({ global: { allowSetTerminals: true } }), 49 | }); 50 | 51 | // Add a few terminals 52 | await jbDirectory 53 | .connect(projectOwner) 54 | .setTerminalsOf(PROJECT_ID, [terminal1.address, terminal2.address]); 55 | 56 | return { projectOwner, deployer, addrs, jbDirectory, terminal1, terminal2 }; 57 | } 58 | 59 | it('Should return primary terminal if set', async function () { 60 | const { projectOwner, jbDirectory, terminal1 } = await setup(); 61 | 62 | let token = ethers.Wallet.createRandom().address; 63 | 64 | await terminal1.mock.token.returns(token); 65 | await terminal1.mock.acceptsToken.withArgs(token, PROJECT_ID).returns(true); 66 | 67 | await jbDirectory 68 | .connect(projectOwner) 69 | .setPrimaryTerminalOf(PROJECT_ID, token, terminal1.address); 70 | 71 | expect(await jbDirectory.connect(projectOwner).primaryTerminalOf(PROJECT_ID, token)).to.equal( 72 | terminal1.address, 73 | ); 74 | }); 75 | 76 | it('Should return terminal with matching token if set', async function () { 77 | const { projectOwner, jbDirectory, terminal1, terminal2 } = await setup(); 78 | 79 | await terminal1.mock.token.returns(ethers.Wallet.createRandom().address); 80 | 81 | let token = ethers.Wallet.createRandom().address; 82 | await terminal2.mock.token.returns(token); 83 | 84 | await terminal1.mock.acceptsToken.withArgs(token, PROJECT_ID).returns(false); 85 | await terminal2.mock.acceptsToken.withArgs(token, PROJECT_ID).returns(true); 86 | 87 | expect(await jbDirectory.connect(projectOwner).primaryTerminalOf(PROJECT_ID, token)).to.equal( 88 | terminal2.address, 89 | ); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/jb_directory/set_is_allowed_to_set_first_controller.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import errors from '../helpers/errors.json'; 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbFundingCycleStore from '../../artifacts/contracts/JBFundingCycleStore.sol/JBFundingCycleStore.json'; 7 | import jbController from '../../artifacts/contracts/interfaces/IJBController.sol/IJBController.json'; 8 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 9 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 10 | 11 | describe('JBDirectory::setIsAllowedToSetFirstController(...)', function () { 12 | async function setup() { 13 | let [deployer, ...addrs] = await ethers.getSigners(); 14 | 15 | let mockJbFundingCycleStore = await deployMockContract(deployer, jbFundingCycleStore.abi); 16 | let mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 17 | let mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 18 | let mockJbController = await deployMockContract(deployer, jbController.abi); 19 | 20 | let jbDirectoryFactory = await ethers.getContractFactory('JBDirectory'); 21 | let jbDirectory = await jbDirectoryFactory.deploy( 22 | mockJbOperatorStore.address, 23 | mockJbProjects.address, 24 | mockJbFundingCycleStore.address, 25 | deployer.address, 26 | ); 27 | 28 | return { 29 | deployer, 30 | addrs, 31 | jbDirectory, 32 | mockJbController, 33 | }; 34 | } 35 | 36 | it('Should add a controller to the list and emit events if caller is JBDirectory owner', async function () { 37 | const { deployer, jbDirectory, mockJbController } = await setup(); 38 | 39 | await expect( 40 | jbDirectory 41 | .connect(deployer) 42 | .setIsAllowedToSetFirstController(mockJbController.address, true), 43 | ) 44 | .to.emit(jbDirectory, 'SetIsAllowedToSetFirstController') 45 | .withArgs(mockJbController.address, true, deployer.address); 46 | 47 | expect(await jbDirectory.isAllowedToSetFirstController(mockJbController.address)).to.be.true; 48 | }); 49 | 50 | it('Should remove a controller and emit events if caller is JBDirectory owner', async function () { 51 | const { deployer, jbDirectory, mockJbController } = await setup(); 52 | 53 | await expect( 54 | jbDirectory 55 | .connect(deployer) 56 | .setIsAllowedToSetFirstController(mockJbController.address, false), 57 | ) 58 | .to.emit(jbDirectory, 'SetIsAllowedToSetFirstController') 59 | .withArgs(mockJbController.address, false, deployer.address); 60 | 61 | expect(await jbDirectory.isAllowedToSetFirstController(mockJbController.address)).to.be.false; 62 | }); 63 | 64 | it("Can't add a controller if caller is not JBDirectory owner", async function () { 65 | const { addrs, jbDirectory, mockJbController } = await setup(); 66 | 67 | await expect( 68 | jbDirectory 69 | .connect(addrs[0]) 70 | .setIsAllowedToSetFirstController(mockJbController.address, true), 71 | ).to.revertedWith('Ownable: caller is not the owner'); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/jb_directory/terminals_of.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | import { packFundingCycleMetadata } from '../helpers/utils'; 6 | 7 | import jbFundingCycleStore from '../../artifacts/contracts/JBFundingCycleStore.sol/JBFundingCycleStore.json'; 8 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 9 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 10 | import jbTerminal from '../../artifacts/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol/JBPayoutRedemptionPaymentTerminal.json'; 11 | 12 | describe('JBDirectory::terminalsOf(...)', function () { 13 | const PROJECT_ID = 13; 14 | 15 | async function setup() { 16 | let [deployer, projectOwner, ...addrs] = await ethers.getSigners(); 17 | 18 | const blockNum = await ethers.provider.getBlockNumber(); 19 | const block = await ethers.provider.getBlock(blockNum); 20 | const timestamp = block.timestamp; 21 | 22 | let mockJbFundingCycleStore = await deployMockContract(deployer, jbFundingCycleStore.abi); 23 | let mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 24 | let mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 25 | 26 | let jbDirectoryFactory = await ethers.getContractFactory('JBDirectory'); 27 | let jbDirectory = await jbDirectoryFactory.deploy( 28 | mockJbOperatorStore.address, 29 | mockJbProjects.address, 30 | mockJbFundingCycleStore.address, 31 | deployer.address, 32 | ); 33 | 34 | let terminal1 = await deployMockContract(projectOwner, jbTerminal.abi); 35 | let terminal2 = await deployMockContract(projectOwner, jbTerminal.abi); 36 | 37 | await mockJbProjects.mock.ownerOf.withArgs(PROJECT_ID).returns(projectOwner.address); 38 | 39 | await mockJbFundingCycleStore.mock.currentOf.withArgs(PROJECT_ID).returns({ 40 | number: 1, 41 | configuration: timestamp, 42 | basedOn: timestamp, 43 | start: timestamp, 44 | duration: 0, 45 | weight: 0, 46 | discountRate: 0, 47 | ballot: ethers.constants.AddressZero, 48 | metadata: packFundingCycleMetadata({ global: { allowSetTerminals: true } }), 49 | }); 50 | 51 | // Add a few terminals 52 | await jbDirectory 53 | .connect(projectOwner) 54 | .setTerminalsOf(PROJECT_ID, [terminal1.address, terminal2.address]); 55 | 56 | return { projectOwner, deployer, addrs, jbDirectory, terminal1, terminal2 }; 57 | } 58 | 59 | it('Should return terminals belonging to the project', async function () { 60 | const { projectOwner, jbDirectory, terminal1, terminal2 } = await setup(); 61 | 62 | let terminals = [...(await jbDirectory.connect(projectOwner).terminalsOf(PROJECT_ID))]; 63 | terminals.sort(); 64 | 65 | let expectedTerminals = [terminal1.address, terminal2.address]; 66 | expectedTerminals.sort(); 67 | 68 | expect(terminals).to.eql(expectedTerminals); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/jb_eth_erc20_splits_payer/set_default_splits.test.js: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat'; 2 | import { expect } from 'chai'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 7 | import jbSplitsStore from '../../artifacts/contracts/JBSplitsStore.sol/JBSplitsStore.json'; 8 | 9 | describe('JBETHERC20SplitsPayer::setDefaultSplits()', function () { 10 | const DEFAULT_PROJECT_ID = 2; 11 | const DEFAULT_SPLITS_PROJECT_ID = 3; 12 | const DEFAULT_SPLITS_DOMAIN = 1; 13 | const DEFAULT_SPLITS_GROUP = 1; 14 | const DEFAULT_BENEFICIARY = ethers.Wallet.createRandom().address; 15 | const DEFAULT_PREFER_CLAIMED_TOKENS = false; 16 | const DEFAULT_MEMO = 'hello world'; 17 | const DEFAULT_METADATA = [0x1]; 18 | const PREFER_ADD_TO_BALANCE = false; 19 | 20 | const NEW_SPLITS_PROJECT_ID = 69; 21 | const NEW_SPLITS_DOMAIN = 420; 22 | const NEW_SPLITS_GROUP = 69420; 23 | 24 | async function setup() { 25 | let [deployer, owner, caller, ...addrs] = await ethers.getSigners(); 26 | 27 | let mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); 28 | let mockJbSplitsStore = await deployMockContract(deployer, jbSplitsStore.abi); 29 | let jbSplitsPayerFactory = await ethers.getContractFactory('contracts/JBETHERC20SplitsPayer.sol:JBETHERC20SplitsPayer'); 30 | 31 | await mockJbSplitsStore.mock.directory.returns(mockJbDirectory.address); 32 | 33 | let jbSplitsPayer = await jbSplitsPayerFactory.deploy( 34 | DEFAULT_SPLITS_PROJECT_ID, 35 | DEFAULT_SPLITS_DOMAIN, 36 | DEFAULT_SPLITS_GROUP, 37 | mockJbSplitsStore.address, 38 | DEFAULT_PROJECT_ID, 39 | DEFAULT_BENEFICIARY, 40 | DEFAULT_PREFER_CLAIMED_TOKENS, 41 | DEFAULT_MEMO, 42 | DEFAULT_METADATA, 43 | PREFER_ADD_TO_BALANCE, 44 | owner.address, 45 | ); 46 | 47 | return { 48 | deployer, 49 | caller, 50 | owner, 51 | addrs, 52 | jbSplitsPayer, 53 | }; 54 | } 55 | 56 | it(`Should set new default splits and emit events`, async function () { 57 | const { owner, jbSplitsPayer } = await setup(); 58 | 59 | await expect( 60 | jbSplitsPayer 61 | .connect(owner) 62 | .setDefaultSplits(NEW_SPLITS_PROJECT_ID, NEW_SPLITS_DOMAIN, NEW_SPLITS_GROUP), 63 | ) 64 | .to.emit(jbSplitsPayer, 'SetDefaultSplits') 65 | .withArgs(NEW_SPLITS_PROJECT_ID, NEW_SPLITS_DOMAIN, NEW_SPLITS_GROUP, owner.address); 66 | 67 | expect(await jbSplitsPayer.defaultSplitsProjectId()).to.equal(NEW_SPLITS_PROJECT_ID); 68 | expect(await jbSplitsPayer.defaultSplitsDomain()).to.equal(NEW_SPLITS_DOMAIN); 69 | expect(await jbSplitsPayer.defaultSplitsGroup()).to.equal(NEW_SPLITS_GROUP); 70 | }); 71 | 72 | it(`Should not change if new default splits equal previous splits, and emit events`, async function () { 73 | const { owner, jbSplitsPayer } = await setup(); 74 | 75 | await expect( 76 | jbSplitsPayer 77 | .connect(owner) 78 | .setDefaultSplits(DEFAULT_SPLITS_PROJECT_ID, DEFAULT_SPLITS_DOMAIN, DEFAULT_SPLITS_GROUP), 79 | ) 80 | .to.emit(jbSplitsPayer, 'SetDefaultSplits') 81 | .withArgs( 82 | DEFAULT_SPLITS_PROJECT_ID, 83 | DEFAULT_SPLITS_DOMAIN, 84 | DEFAULT_SPLITS_GROUP, 85 | owner.address, 86 | ); 87 | 88 | expect(await jbSplitsPayer.defaultSplitsProjectId()).to.equal(DEFAULT_SPLITS_PROJECT_ID); 89 | expect(await jbSplitsPayer.defaultSplitsDomain()).to.equal(DEFAULT_SPLITS_DOMAIN); 90 | expect(await jbSplitsPayer.defaultSplitsGroup()).to.equal(DEFAULT_SPLITS_GROUP); 91 | }); 92 | 93 | it(`Cannot change default splits if caller is not the owner`, async function () { 94 | const { caller, jbSplitsPayer } = await setup(); 95 | 96 | await expect( 97 | jbSplitsPayer 98 | .connect(caller) 99 | .setDefaultSplits(DEFAULT_SPLITS_PROJECT_ID, DEFAULT_SPLITS_DOMAIN, DEFAULT_SPLITS_GROUP), 100 | ).to.be.revertedWith('Ownable: caller is not the owner'); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/jb_eth_erc20_splits_payer_deployer/deploy_split_payer.test.js: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat'; 2 | import { expect } from 'chai'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 7 | import jbSplitsStore from '../../artifacts/contracts/JBSplitsStore.sol/JBSplitsStore.json'; 8 | 9 | describe('JBSplitsPayerDeployer::deploySplitsPayer(...)', function () { 10 | const DEFAULT_PROJECT_ID = 2; 11 | const DEFAULT_SPLITS_PROJECT_ID = 3; 12 | const DEFAULT_SPLITS_DOMAIN = 2; 13 | const DEFAULT_SPLITS_GROUP = 4; 14 | const DEFAULT_BENEFICIARY = ethers.Wallet.createRandom().address; 15 | const DEFAULT_PREFER_CLAIMED_TOKENS = true; 16 | const DEFAULT_MEMO = 'hello world'; 17 | const DEFAULT_METADATA = '0x69'; 18 | const PREFER_ADD_TO_BALANCE = true; 19 | 20 | async function setup() { 21 | let [deployer, owner, ...addrs] = await ethers.getSigners(); 22 | 23 | let mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); 24 | let mockJbSplitsStore = await deployMockContract(deployer, jbSplitsStore.abi); 25 | let jbSplitsPayerDeployerFactory = await ethers.getContractFactory( 26 | 'contracts/JBETHERC20SplitsPayerDeployer.sol:JBETHERC20SplitsPayerDeployer', 27 | ); 28 | let jbSplitsPayerDeployer = await jbSplitsPayerDeployerFactory.deploy(); 29 | 30 | await mockJbSplitsStore.mock.directory.returns(mockJbDirectory.address); 31 | 32 | return { 33 | deployer, 34 | owner, 35 | jbSplitsPayerDeployer, 36 | mockJbSplitsStore, 37 | }; 38 | } 39 | 40 | it(`Should deploy and emit event`, async function () { 41 | let { deployer, owner, jbSplitsPayerDeployer, mockJbSplitsStore } = await setup(); 42 | 43 | const currentNonce = await ethers.provider.getTransactionCount(jbSplitsPayerDeployer.address); 44 | const splitsPayerAddress = ethers.utils.getContractAddress({ 45 | from: jbSplitsPayerDeployer.address, 46 | nonce: currentNonce, 47 | }); 48 | 49 | let tx = await jbSplitsPayerDeployer 50 | .connect(deployer) 51 | .deploySplitsPayer( 52 | DEFAULT_SPLITS_PROJECT_ID, 53 | DEFAULT_SPLITS_DOMAIN, 54 | DEFAULT_SPLITS_GROUP, 55 | mockJbSplitsStore.address, 56 | DEFAULT_PROJECT_ID, 57 | DEFAULT_BENEFICIARY, 58 | DEFAULT_PREFER_CLAIMED_TOKENS, 59 | DEFAULT_MEMO, 60 | DEFAULT_METADATA, 61 | PREFER_ADD_TO_BALANCE, 62 | owner.address, 63 | ); 64 | 65 | await expect(tx) 66 | .to.emit(jbSplitsPayerDeployer, 'DeploySplitsPayer') 67 | .withArgs( 68 | splitsPayerAddress, 69 | DEFAULT_SPLITS_PROJECT_ID, 70 | DEFAULT_SPLITS_DOMAIN, 71 | DEFAULT_SPLITS_GROUP, 72 | mockJbSplitsStore.address, 73 | DEFAULT_PROJECT_ID, 74 | DEFAULT_BENEFICIARY, 75 | DEFAULT_PREFER_CLAIMED_TOKENS, 76 | DEFAULT_MEMO, 77 | DEFAULT_METADATA, 78 | PREFER_ADD_TO_BALANCE, 79 | owner.address, 80 | deployer.address, 81 | ); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/jb_operator_store/set_operator.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { makePackedPermissions } from '../helpers/utils'; 5 | import errors from '../helpers/errors.json'; 6 | 7 | describe('JBOperatorStore::setOperator(...)', function () { 8 | const DOMAIN = 1; 9 | const PERMISSION_INDEXES_EMPTY = []; 10 | const PERMISSION_INDEXES_1 = [1, 2, 3]; 11 | const PERMISSION_INDEXES_2 = [4, 5, 6]; 12 | const PERMISSION_INDEXES_OUT_OF_BOUND = [1, 2, 256]; 13 | 14 | async function setup() { 15 | let [deployer, projectOwner, ...addrs] = await ethers.getSigners(); 16 | 17 | let jbOperatorStoreFactory = await ethers.getContractFactory('JBOperatorStore'); 18 | let jbOperatorStore = await jbOperatorStoreFactory.deploy(); 19 | 20 | return { 21 | projectOwner, 22 | deployer, 23 | addrs, 24 | jbOperatorStore, 25 | }; 26 | } 27 | 28 | async function setOperatorAndValidateEvent( 29 | jbOperatorStore, 30 | operator, 31 | account, 32 | domain, 33 | permissionIndexes, 34 | packedPermissionIndexes, 35 | ) { 36 | const tx = await jbOperatorStore 37 | .connect(account) 38 | .setOperator([ 39 | /*operator=*/ operator.address, 40 | /*domain=*/ domain, 41 | /*permissionsIndexes=*/ permissionIndexes, 42 | ]); 43 | 44 | await expect(tx) 45 | .to.emit(jbOperatorStore, 'SetOperator') 46 | .withArgs( 47 | operator.address, 48 | account.address, 49 | domain, 50 | permissionIndexes, 51 | packedPermissionIndexes, 52 | ); 53 | 54 | expect(await jbOperatorStore.permissionsOf(operator.address, account.address, domain)).to.equal( 55 | packedPermissionIndexes, 56 | ); 57 | } 58 | 59 | it('Set operator with no previous value, override it, and clear it', async function () { 60 | const { deployer, projectOwner, jbOperatorStore } = await setup(); 61 | 62 | await setOperatorAndValidateEvent( 63 | jbOperatorStore, 64 | projectOwner, 65 | /*account=*/ deployer, 66 | DOMAIN, 67 | PERMISSION_INDEXES_1, 68 | makePackedPermissions(PERMISSION_INDEXES_1), 69 | ); 70 | 71 | await setOperatorAndValidateEvent( 72 | jbOperatorStore, 73 | projectOwner, 74 | /*account=*/ deployer, 75 | DOMAIN, 76 | PERMISSION_INDEXES_2, 77 | makePackedPermissions(PERMISSION_INDEXES_2), 78 | ); 79 | 80 | await setOperatorAndValidateEvent( 81 | jbOperatorStore, 82 | projectOwner, 83 | /*account=*/ deployer, 84 | DOMAIN, 85 | PERMISSION_INDEXES_EMPTY, 86 | makePackedPermissions(PERMISSION_INDEXES_EMPTY), 87 | ); 88 | }); 89 | 90 | it('Index out of bounds', async function () { 91 | const { deployer, projectOwner, jbOperatorStore } = await setup(); 92 | let permissionIndexes = [1, 2, 256]; 93 | 94 | await expect( 95 | jbOperatorStore 96 | .connect(deployer) 97 | .setOperator([projectOwner.address, DOMAIN, PERMISSION_INDEXES_OUT_OF_BOUND]), 98 | ).to.be.revertedWith(errors.PERMISSION_INDEX_OUT_OF_BOUNDS); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/jb_payment_terminal/1/set_fee.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import errors from '../../helpers/errors.json'; 4 | 5 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 6 | 7 | import jbDirectory from '../../../artifacts/contracts/interfaces/IJBDirectory.sol/IJBDirectory.json'; 8 | import jbPaymentTerminalStore from '../../../artifacts/contracts/JBSingleTokenPaymentTerminalStore.sol/JBSingleTokenPaymentTerminalStore.json'; 9 | import jbOperatoreStore from '../../../artifacts/contracts/interfaces/IJBOperatorStore.sol/IJBOperatorStore.json'; 10 | import jbProjects from '../../../artifacts/contracts/interfaces/IJBProjects.sol/IJBProjects.json'; 11 | import jbSplitsStore from '../../../artifacts/contracts/interfaces/IJBSplitsStore.sol/IJBSplitsStore.json'; 12 | import jbPrices from '../../../artifacts/contracts/interfaces/IJBPrices.sol/IJBPrices.json'; 13 | 14 | describe('JBPayoutRedemptionPaymentTerminal::setFee(...)', function () { 15 | const NEW_FEE = 8; // 4% 16 | 17 | async function setup() { 18 | let [deployer, terminalOwner, caller] = await ethers.getSigners(); 19 | 20 | let [ 21 | mockJbDirectory, 22 | mockJBPaymentTerminalStore, 23 | mockJbOperatorStore, 24 | mockJbProjects, 25 | mockJbSplitsStore, 26 | mockJbPrices, 27 | ] = await Promise.all([ 28 | deployMockContract(deployer, jbDirectory.abi), 29 | deployMockContract(deployer, jbPaymentTerminalStore.abi), 30 | deployMockContract(deployer, jbOperatoreStore.abi), 31 | deployMockContract(deployer, jbProjects.abi), 32 | deployMockContract(deployer, jbSplitsStore.abi), 33 | deployMockContract(deployer, jbPrices.abi), 34 | ]); 35 | 36 | const jbCurrenciesFactory = await ethers.getContractFactory('JBCurrencies'); 37 | const jbCurrencies = await jbCurrenciesFactory.deploy(); 38 | const CURRENCY_ETH = await jbCurrencies.ETH(); 39 | 40 | let jbTerminalFactory = await ethers.getContractFactory( 41 | 'contracts/JBETHPaymentTerminal.sol:JBETHPaymentTerminal', 42 | deployer, 43 | ); 44 | 45 | let jbEthPaymentTerminal = await jbTerminalFactory 46 | .connect(deployer) 47 | .deploy( 48 | CURRENCY_ETH, 49 | mockJbOperatorStore.address, 50 | mockJbProjects.address, 51 | mockJbDirectory.address, 52 | mockJbSplitsStore.address, 53 | mockJbPrices.address, 54 | mockJBPaymentTerminalStore.address, 55 | terminalOwner.address, 56 | ); 57 | 58 | return { 59 | jbEthPaymentTerminal, 60 | terminalOwner, 61 | caller, 62 | }; 63 | } 64 | 65 | it('Should set new fee and emit event if caller is terminal owner', async function () { 66 | const { jbEthPaymentTerminal, terminalOwner } = await setup(); 67 | 68 | expect(await jbEthPaymentTerminal.connect(terminalOwner).setFee(NEW_FEE)) 69 | .to.emit(jbEthPaymentTerminal, 'SetFee') 70 | .withArgs(NEW_FEE, terminalOwner.address); 71 | }); 72 | 73 | it("Can't set fee above 5%", async function () { 74 | const { jbEthPaymentTerminal, terminalOwner } = await setup(); 75 | await expect(jbEthPaymentTerminal.connect(terminalOwner).setFee(50_000_001)) // 5.0000001% (out of 1,000,000,000) 76 | .to.be.revertedWith(errors.FEE_TOO_HIGH); 77 | }); 78 | 79 | it("Can't set fee if caller is not owner", async function () { 80 | const { jbEthPaymentTerminal, caller } = await setup(); 81 | await expect(jbEthPaymentTerminal.connect(caller).setFee(40_000_000)).to.be.revertedWith( 82 | 'Ownable: caller is not the owner', 83 | ); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/jb_payment_terminal/1/set_fee_gauge.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../../artifacts/contracts/interfaces/IJBDirectory.sol/IJBDirectory.json'; 7 | import jbPaymentTerminalStore from '../../../artifacts/contracts/JBSingleTokenPaymentTerminalStore.sol/JBSingleTokenPaymentTerminalStore.json'; 8 | import jbFeeGauge from '../../../artifacts/contracts/interfaces/IJBFeeGauge.sol/IJBFeeGauge.json'; 9 | import jbOperatoreStore from '../../../artifacts/contracts/interfaces/IJBOperatorStore.sol/IJBOperatorStore.json'; 10 | import jbProjects from '../../../artifacts/contracts/interfaces/IJBProjects.sol/IJBProjects.json'; 11 | import jbSplitsStore from '../../../artifacts/contracts/interfaces/IJBSplitsStore.sol/IJBSplitsStore.json'; 12 | import jbPrices from '../../../artifacts/contracts/interfaces/IJBPrices.sol/IJBPrices.json'; 13 | 14 | describe('JBPayoutRedemptionPaymentTerminal::setFeeGauge(...)', function () { 15 | async function setup() { 16 | let [deployer, terminalOwner, caller] = await ethers.getSigners(); 17 | 18 | let [ 19 | mockJbDirectory, 20 | mockJBPaymentTerminalStore, 21 | mockJbFeeGauge, 22 | mockJbOperatorStore, 23 | mockJbProjects, 24 | mockJbSplitsStore, 25 | mockJbPrices, 26 | ] = await Promise.all([ 27 | deployMockContract(deployer, jbDirectory.abi), 28 | deployMockContract(deployer, jbPaymentTerminalStore.abi), 29 | deployMockContract(deployer, jbFeeGauge.abi), 30 | deployMockContract(deployer, jbOperatoreStore.abi), 31 | deployMockContract(deployer, jbProjects.abi), 32 | deployMockContract(deployer, jbSplitsStore.abi), 33 | deployMockContract(deployer, jbPrices.abi), 34 | ]); 35 | 36 | const jbCurrenciesFactory = await ethers.getContractFactory('JBCurrencies'); 37 | const jbCurrencies = await jbCurrenciesFactory.deploy(); 38 | const CURRENCY_ETH = await jbCurrencies.ETH(); 39 | 40 | let jbTerminalFactory = await ethers.getContractFactory( 41 | 'contracts/JBETHPaymentTerminal.sol:JBETHPaymentTerminal', 42 | deployer, 43 | ); 44 | 45 | let jbEthPaymentTerminal = await jbTerminalFactory 46 | .connect(deployer) 47 | .deploy( 48 | CURRENCY_ETH, 49 | mockJbOperatorStore.address, 50 | mockJbProjects.address, 51 | mockJbDirectory.address, 52 | mockJbSplitsStore.address, 53 | mockJbPrices.address, 54 | mockJBPaymentTerminalStore.address, 55 | terminalOwner.address, 56 | ); 57 | 58 | return { 59 | terminalOwner, 60 | caller, 61 | jbEthPaymentTerminal, 62 | mockJbFeeGauge, 63 | }; 64 | } 65 | 66 | it('Should set the fee gauge and emit event if caller is terminal owner', async function () { 67 | const { terminalOwner, jbEthPaymentTerminal, mockJbFeeGauge } = await setup(); 68 | 69 | expect(await jbEthPaymentTerminal.connect(terminalOwner).setFeeGauge(mockJbFeeGauge.address)) 70 | .to.emit(jbEthPaymentTerminal, 'SetFeeGauge') 71 | .withArgs(mockJbFeeGauge.address, terminalOwner.address); 72 | }); 73 | it("Can't set the fee gauge if caller is not the terminal owner", async function () { 74 | const { caller, jbEthPaymentTerminal, mockJbFeeGauge } = await setup(); 75 | 76 | await expect( 77 | jbEthPaymentTerminal.connect(caller).setFeeGauge(mockJbFeeGauge.address), 78 | ).to.be.revertedWith('Ownable: caller is not the owner'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/jb_payment_terminal/1/set_feeless_terminal.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../../artifacts/contracts/interfaces/IJBDirectory.sol/IJBDirectory.json'; 7 | import JBEthPaymentTerminal from '../../../artifacts/contracts/JBETHPaymentTerminal.sol/JBETHPaymentTerminal.json'; 8 | import jbPaymentTerminalStore from '../../../artifacts/contracts/JBSingleTokenPaymentTerminalStore.sol/JBSingleTokenPaymentTerminalStore.json'; 9 | import jbOperatoreStore from '../../../artifacts/contracts/interfaces/IJBOperatorStore.sol/IJBOperatorStore.json'; 10 | import jbProjects from '../../../artifacts/contracts/interfaces/IJBProjects.sol/IJBProjects.json'; 11 | import jbSplitsStore from '../../../artifacts/contracts/interfaces/IJBSplitsStore.sol/IJBSplitsStore.json'; 12 | import jbPrices from '../../../artifacts/contracts/interfaces/IJBPrices.sol/IJBPrices.json'; 13 | 14 | describe('JBPayoutRedemptionPaymentTerminal::setFeelessAddress(...)', function () { 15 | async function setup() { 16 | let [deployer, terminalOwner, caller] = await ethers.getSigners(); 17 | 18 | let [ 19 | mockJbDirectory, 20 | mockJbEthPaymentTerminal, 21 | mockJBPaymentTerminalStore, 22 | mockJbOperatorStore, 23 | mockJbProjects, 24 | mockJbSplitsStore, 25 | mockJbPrices, 26 | ] = await Promise.all([ 27 | deployMockContract(deployer, jbDirectory.abi), 28 | deployMockContract(deployer, JBEthPaymentTerminal.abi), 29 | deployMockContract(deployer, jbPaymentTerminalStore.abi), 30 | deployMockContract(deployer, jbOperatoreStore.abi), 31 | deployMockContract(deployer, jbProjects.abi), 32 | deployMockContract(deployer, jbSplitsStore.abi), 33 | deployMockContract(deployer, jbPrices.abi), 34 | ]); 35 | 36 | const jbCurrenciesFactory = await ethers.getContractFactory('JBCurrencies'); 37 | const jbCurrencies = await jbCurrenciesFactory.deploy(); 38 | const CURRENCY_ETH = await jbCurrencies.ETH(); 39 | 40 | let jbTerminalFactory = await ethers.getContractFactory( 41 | 'contracts/JBETHPaymentTerminal.sol:JBETHPaymentTerminal', 42 | deployer, 43 | ); 44 | 45 | let jbEthPaymentTerminal = await jbTerminalFactory 46 | .connect(deployer) 47 | .deploy( 48 | CURRENCY_ETH, 49 | mockJbOperatorStore.address, 50 | mockJbProjects.address, 51 | mockJbDirectory.address, 52 | mockJbSplitsStore.address, 53 | mockJbPrices.address, 54 | mockJBPaymentTerminalStore.address, 55 | terminalOwner.address, 56 | ); 57 | 58 | return { 59 | terminalOwner, 60 | caller, 61 | jbEthPaymentTerminal, 62 | mockJbEthPaymentTerminal, 63 | }; 64 | } 65 | 66 | it('Should add a terminal as feeless and emit event', async function () { 67 | const { terminalOwner, jbEthPaymentTerminal, mockJbEthPaymentTerminal } = await setup(); 68 | 69 | expect( 70 | await jbEthPaymentTerminal 71 | .connect(terminalOwner) 72 | .setFeelessAddress(mockJbEthPaymentTerminal.address, true), 73 | ) 74 | .to.emit(jbEthPaymentTerminal, 'SetFeelessAddress') 75 | .withArgs(mockJbEthPaymentTerminal.address, true, terminalOwner.address); 76 | 77 | expect(await jbEthPaymentTerminal.isFeelessAddress(mockJbEthPaymentTerminal.address)).to.be 78 | .true; 79 | }); 80 | 81 | it('Should remove a terminal as feeless and emit event', async function () { 82 | const { terminalOwner, jbEthPaymentTerminal, mockJbEthPaymentTerminal } = await setup(); 83 | 84 | await jbEthPaymentTerminal 85 | .connect(terminalOwner) 86 | .setFeelessAddress(mockJbEthPaymentTerminal.address, true); 87 | 88 | expect( 89 | await jbEthPaymentTerminal 90 | .connect(terminalOwner) 91 | .setFeelessAddress(mockJbEthPaymentTerminal.address, false), 92 | ) 93 | .to.emit(jbEthPaymentTerminal, 'SetFeelessAddress') 94 | .withArgs(mockJbEthPaymentTerminal.address, false, terminalOwner.address); 95 | 96 | expect(await jbEthPaymentTerminal.isFeelessAddress(mockJbEthPaymentTerminal.address)).to.be 97 | .false; 98 | }); 99 | 100 | it('Cannot set a feeless terminal if caller is not the owner', async function () { 101 | const { caller, jbEthPaymentTerminal, mockJbEthPaymentTerminal } = await setup(); 102 | await expect( 103 | jbEthPaymentTerminal 104 | .connect(caller) 105 | .setFeelessAddress(mockJbEthPaymentTerminal.address, true), 106 | ).to.be.revertedWith('Ownable: caller is not the owner'); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/jb_payment_terminal/2/set_fee.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import errors from '../../helpers/errors.json'; 4 | 5 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 6 | 7 | import jbDirectory from '../../../artifacts/contracts/interfaces/IJBDirectory.sol/IJBDirectory.json'; 8 | import jbPaymentTerminalStore from '../../../artifacts/contracts/JBSingleTokenPaymentTerminalStore.sol/JBSingleTokenPaymentTerminalStore.json'; 9 | import jbOperatoreStore from '../../../artifacts/contracts/interfaces/IJBOperatorStore.sol/IJBOperatorStore.json'; 10 | import jbProjects from '../../../artifacts/contracts/interfaces/IJBProjects.sol/IJBProjects.json'; 11 | import jbSplitsStore from '../../../artifacts/contracts/interfaces/IJBSplitsStore.sol/IJBSplitsStore.json'; 12 | import jbPrices from '../../../artifacts/contracts/interfaces/IJBPrices.sol/IJBPrices.json'; 13 | 14 | describe('JBPayoutRedemptionPaymentTerminal::setFee(...)', function () { 15 | const NEW_FEE = 8; // 4% 16 | 17 | async function setup() { 18 | let [deployer, terminalOwner, caller] = await ethers.getSigners(); 19 | 20 | let [ 21 | mockJbDirectory, 22 | mockJBPaymentTerminalStore, 23 | mockJbOperatorStore, 24 | mockJbProjects, 25 | mockJbSplitsStore, 26 | mockJbPrices, 27 | ] = await Promise.all([ 28 | deployMockContract(deployer, jbDirectory.abi), 29 | deployMockContract(deployer, jbPaymentTerminalStore.abi), 30 | deployMockContract(deployer, jbOperatoreStore.abi), 31 | deployMockContract(deployer, jbProjects.abi), 32 | deployMockContract(deployer, jbSplitsStore.abi), 33 | deployMockContract(deployer, jbPrices.abi), 34 | ]); 35 | 36 | const jbCurrenciesFactory = await ethers.getContractFactory('JBCurrencies'); 37 | const jbCurrencies = await jbCurrenciesFactory.deploy(); 38 | const CURRENCY_ETH = await jbCurrencies.ETH(); 39 | 40 | let jbTerminalFactory = await ethers.getContractFactory( 41 | 'contracts/JBETHPaymentTerminal.sol:JBETHPaymentTerminal', 42 | deployer, 43 | ); 44 | 45 | let jbEthPaymentTerminal = await jbTerminalFactory 46 | .connect(deployer) 47 | .deploy( 48 | CURRENCY_ETH, 49 | mockJbOperatorStore.address, 50 | mockJbProjects.address, 51 | mockJbDirectory.address, 52 | mockJbSplitsStore.address, 53 | mockJbPrices.address, 54 | mockJBPaymentTerminalStore.address, 55 | terminalOwner.address, 56 | ); 57 | 58 | return { 59 | jbEthPaymentTerminal, 60 | terminalOwner, 61 | caller, 62 | }; 63 | } 64 | 65 | it('Should set new fee and emit event if caller is terminal owner', async function () { 66 | const { jbEthPaymentTerminal, terminalOwner } = await setup(); 67 | 68 | expect(await jbEthPaymentTerminal.connect(terminalOwner).setFee(NEW_FEE)) 69 | .to.emit(jbEthPaymentTerminal, 'SetFee') 70 | .withArgs(NEW_FEE, terminalOwner.address); 71 | }); 72 | 73 | it("Can't set fee above 5%", async function () { 74 | const { jbEthPaymentTerminal, terminalOwner } = await setup(); 75 | await expect(jbEthPaymentTerminal.connect(terminalOwner).setFee(50_000_001)) // 5.0000001% (out of 1,000,000,000) 76 | .to.be.revertedWith(errors.FEE_TOO_HIGH); 77 | }); 78 | 79 | it("Can't set fee if caller is not owner", async function () { 80 | const { jbEthPaymentTerminal, caller } = await setup(); 81 | await expect(jbEthPaymentTerminal.connect(caller).setFee(40_000_000)).to.be.revertedWith( 82 | 'Ownable: caller is not the owner', 83 | ); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/jb_payment_terminal/2/set_fee_gauge.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../../artifacts/contracts/interfaces/IJBDirectory.sol/IJBDirectory.json'; 7 | import jbPaymentTerminalStore from '../../../artifacts/contracts/JBSingleTokenPaymentTerminalStore.sol/JBSingleTokenPaymentTerminalStore.json'; 8 | import jbFeeGauge from '../../../artifacts/contracts/interfaces/IJBFeeGauge.sol/IJBFeeGauge.json'; 9 | import jbOperatoreStore from '../../../artifacts/contracts/interfaces/IJBOperatorStore.sol/IJBOperatorStore.json'; 10 | import jbProjects from '../../../artifacts/contracts/interfaces/IJBProjects.sol/IJBProjects.json'; 11 | import jbSplitsStore from '../../../artifacts/contracts/interfaces/IJBSplitsStore.sol/IJBSplitsStore.json'; 12 | import jbPrices from '../../../artifacts/contracts/interfaces/IJBPrices.sol/IJBPrices.json'; 13 | 14 | describe('JBPayoutRedemptionPaymentTerminal::setFeeGauge(...)', function () { 15 | async function setup() { 16 | let [deployer, terminalOwner, caller] = await ethers.getSigners(); 17 | 18 | let [ 19 | mockJbDirectory, 20 | mockJBPaymentTerminalStore, 21 | mockJbFeeGauge, 22 | mockJbOperatorStore, 23 | mockJbProjects, 24 | mockJbSplitsStore, 25 | mockJbPrices, 26 | ] = await Promise.all([ 27 | deployMockContract(deployer, jbDirectory.abi), 28 | deployMockContract(deployer, jbPaymentTerminalStore.abi), 29 | deployMockContract(deployer, jbFeeGauge.abi), 30 | deployMockContract(deployer, jbOperatoreStore.abi), 31 | deployMockContract(deployer, jbProjects.abi), 32 | deployMockContract(deployer, jbSplitsStore.abi), 33 | deployMockContract(deployer, jbPrices.abi), 34 | ]); 35 | 36 | const jbCurrenciesFactory = await ethers.getContractFactory('JBCurrencies'); 37 | const jbCurrencies = await jbCurrenciesFactory.deploy(); 38 | const CURRENCY_ETH = await jbCurrencies.ETH(); 39 | 40 | let jbTerminalFactory = await ethers.getContractFactory( 41 | 'contracts/JBETHPaymentTerminal.sol:JBETHPaymentTerminal', 42 | deployer, 43 | ); 44 | 45 | let jbEthPaymentTerminal = await jbTerminalFactory 46 | .connect(deployer) 47 | .deploy( 48 | CURRENCY_ETH, 49 | mockJbOperatorStore.address, 50 | mockJbProjects.address, 51 | mockJbDirectory.address, 52 | mockJbSplitsStore.address, 53 | mockJbPrices.address, 54 | mockJBPaymentTerminalStore.address, 55 | terminalOwner.address, 56 | ); 57 | 58 | return { 59 | terminalOwner, 60 | caller, 61 | jbEthPaymentTerminal, 62 | mockJbFeeGauge, 63 | }; 64 | } 65 | 66 | it('Should set the fee gauge and emit event if caller is terminal owner', async function () { 67 | const { terminalOwner, jbEthPaymentTerminal, mockJbFeeGauge } = await setup(); 68 | 69 | expect(await jbEthPaymentTerminal.connect(terminalOwner).setFeeGauge(mockJbFeeGauge.address)) 70 | .to.emit(jbEthPaymentTerminal, 'SetFeeGauge') 71 | .withArgs(mockJbFeeGauge.address, terminalOwner.address); 72 | }); 73 | it("Can't set the fee gauge if caller is not the terminal owner", async function () { 74 | const { caller, jbEthPaymentTerminal, mockJbFeeGauge } = await setup(); 75 | 76 | await expect( 77 | jbEthPaymentTerminal.connect(caller).setFeeGauge(mockJbFeeGauge.address), 78 | ).to.be.revertedWith('Ownable: caller is not the owner'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/jb_prices/add_feed_for.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import jbChainlinkPriceFeed from '../../artifacts/contracts/JBChainlinkV3PriceFeed.sol/JBChainlinkV3PriceFeed.json'; 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | import errors from '../helpers/errors.json'; 6 | 7 | describe('JBPrices::addFeed(...)', function () { 8 | let deployer; 9 | let addrs; 10 | 11 | let priceFeed; 12 | 13 | let jbPricesFactory; 14 | let jbPrices; 15 | 16 | beforeEach(async function () { 17 | [deployer, ...addrs] = await ethers.getSigners(); 18 | 19 | priceFeed = await deployMockContract(deployer, jbChainlinkPriceFeed.abi); 20 | 21 | jbPricesFactory = await ethers.getContractFactory('JBPrices'); 22 | jbPrices = await jbPricesFactory.deploy(deployer.address); 23 | }); 24 | 25 | it('Add feed from owner succeeds, but fails if added again', async function () { 26 | let currency = 1; 27 | let base = 2; 28 | 29 | // Add a feed for an arbitrary currency. 30 | let tx = await jbPrices.connect(deployer).addFeedFor(currency, base, priceFeed.address); 31 | 32 | // Expect an event to have been emitted. 33 | await expect(tx).to.emit(jbPrices, 'AddFeed').withArgs(currency, base, priceFeed.address); 34 | 35 | // Get the stored feed. 36 | const storedFeed = await jbPrices.feedFor(currency, base); 37 | 38 | // Expect the stored feed values to match. 39 | expect(storedFeed).to.equal(priceFeed.address); 40 | 41 | // Try to add the same feed again. It should fail with an error indicating that it already 42 | // exists. 43 | await expect( 44 | jbPrices.connect(deployer).addFeedFor(currency, base, priceFeed.address), 45 | ).to.be.revertedWith(errors.PRICE_FEED_ALREADY_EXISTS); 46 | }); 47 | 48 | it('Add feed from address other than owner fails', async function () { 49 | await expect( 50 | jbPrices 51 | .connect(addrs[0]) // Arbitrary address. 52 | .addFeedFor(/*currency=*/ 1, /*base=*/ 2, priceFeed.address), 53 | ).to.be.revertedWith('Ownable: caller is not the owner'); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/jb_prices/price_for.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import jbChainlinkPriceFeed from '../../artifacts/contracts/JBChainlinkV3PriceFeed.sol/JBChainlinkV3PriceFeed.json'; 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | import { BigNumber } from '@ethersproject/bignumber'; 6 | import errors from '../helpers/errors.json'; 7 | 8 | describe('JBPrices::priceFor(...)', function () { 9 | const DECIMALS = 18; 10 | 11 | let deployer; 12 | let addrs; 13 | 14 | let priceFeed; 15 | 16 | let jbPricesFactory; 17 | let jbPrices; 18 | 19 | beforeEach(async function () { 20 | [deployer, ...addrs] = await ethers.getSigners(); 21 | 22 | priceFeed = await deployMockContract(deployer, jbChainlinkPriceFeed.abi); 23 | 24 | jbPricesFactory = await ethers.getContractFactory('JBPrices'); 25 | jbPrices = await jbPricesFactory.deploy(deployer.address); 26 | }); 27 | 28 | /** 29 | * Initialiazes mock price feed, adds it to JBPrices, and returns the fetched result. 30 | */ 31 | async function addFeedAndFetchPrice(price, currency, base) { 32 | await priceFeed.mock.currentPrice.withArgs(DECIMALS).returns(price); 33 | 34 | await jbPrices.connect(deployer).addFeedFor(currency, base, priceFeed.address); 35 | return await jbPrices.connect(deployer).priceFor(currency, base, DECIMALS); 36 | } 37 | 38 | it('Should return 1 for the same base and currency, with correct decimals', async function () { 39 | expect(await addFeedAndFetchPrice(/*price=*/ 400, /*currency=*/ 1, /*base=*/ 1)).to.equal( 40 | ethers.BigNumber.from(10).pow(DECIMALS), 41 | ); 42 | }); 43 | 44 | it('Should return the correct price', async function () { 45 | let price = 4000; 46 | expect(await addFeedAndFetchPrice(price, /*currency=*/ 1, /*base=*/ 2, DECIMALS)).to.equal( 47 | ethers.BigNumber.from(price), 48 | ); 49 | }); 50 | 51 | it('Should return the inverse of the price, if only the inverse feed is available', async function () { 52 | let price = ethers.BigNumber.from(4000); 53 | 54 | await priceFeed.mock.currentPrice 55 | .withArgs(DECIMALS) 56 | .returns(price.mul(ethers.BigNumber.from(10).pow(DECIMALS))); 57 | 58 | await jbPrices.connect(deployer).addFeedFor(/*currency=*/ 1, /*base=*/ 2, priceFeed.address); 59 | 60 | expect(await jbPrices.priceFor(/*base=*/ 2, /*currency=*/ 1, DECIMALS)).to.equal( 61 | ethers.BigNumber.from(10).pow(DECIMALS).div(price), 62 | ); 63 | }); 64 | 65 | it('Feed not found', async function () { 66 | await expect( 67 | jbPrices.connect(deployer).priceFor(/*currency=*/ 1, /*base=*/ 7, DECIMALS), 68 | ).to.be.revertedWith(errors.PRICE_FEED_NOT_FOUND); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/jb_project_payer_deployer/deploy_project_payer.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 7 | 8 | describe('JBProjectPayerDeployer::deployProjectPayer(...)', function () { 9 | const INITIAL_PROJECT_ID = 1; 10 | const INITIAL_BENEFICIARY = ethers.Wallet.createRandom().address; 11 | const INITIAL_PREFER_CLAIMED_TOKENS = false; 12 | const INITIAL_PREFER_ADD_TO_BALANCE = false; 13 | const INITIAL_MEMO = 'hello world'; 14 | const INITIAL_METADATA = '0x69'; 15 | 16 | async function setup() { 17 | const [deployer, owner] = await ethers.getSigners(); 18 | 19 | const mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); 20 | 21 | const jbProjectPayerDeployerFactory = await ethers.getContractFactory( 22 | 'JBETHERC20ProjectPayerDeployer', 23 | ); 24 | const jbProjectPayerDeployer = await jbProjectPayerDeployerFactory.deploy(); 25 | 26 | return { 27 | owner, 28 | mockJbDirectory, 29 | jbProjectPayerDeployer, 30 | }; 31 | } 32 | 33 | it('Should deploy the project payer', async function () { 34 | const { owner, mockJbDirectory, jbProjectPayerDeployer } = await setup(); 35 | 36 | const currentNonce = await ethers.provider.getTransactionCount(jbProjectPayerDeployer.address); 37 | const jbProjectPayerAddress = ethers.utils.getContractAddress({ 38 | from: jbProjectPayerDeployer.address, 39 | nonce: currentNonce, 40 | }); 41 | 42 | const tx = await jbProjectPayerDeployer 43 | .connect(owner) 44 | .deployProjectPayer( 45 | INITIAL_PROJECT_ID, 46 | INITIAL_BENEFICIARY, 47 | INITIAL_PREFER_CLAIMED_TOKENS, 48 | INITIAL_MEMO, 49 | INITIAL_METADATA, 50 | INITIAL_PREFER_ADD_TO_BALANCE, 51 | mockJbDirectory.address, 52 | owner.address, 53 | ); 54 | 55 | await expect(tx) 56 | .to.emit(jbProjectPayerDeployer, 'DeployProjectPayer') 57 | .withArgs( 58 | jbProjectPayerAddress, 59 | INITIAL_PROJECT_ID, 60 | INITIAL_BENEFICIARY, 61 | INITIAL_PREFER_CLAIMED_TOKENS, 62 | INITIAL_MEMO, 63 | INITIAL_METADATA, 64 | INITIAL_PREFER_ADD_TO_BALANCE, 65 | mockJbDirectory.address, 66 | owner.address, 67 | owner.address, 68 | ); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/jb_projects/create_for.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | describe('JBProjects::createFor(...)', function () { 5 | const METADATA_CID = 'QmThsKQpFBQicz3t3SU9rRz3GV81cwjnWsBBLxzznRNvpa'; 6 | const METADATA_DOMAIN = 1234; 7 | const PROJECT_ID_1 = 1; 8 | const PROJECT_ID_2 = 2; 9 | 10 | async function setup() { 11 | let [deployer, projectOwner, ...addrs] = await ethers.getSigners(); 12 | 13 | let jbOperatorStoreFactory = await ethers.getContractFactory('JBOperatorStore'); 14 | let jbOperatorStore = await jbOperatorStoreFactory.deploy(); 15 | 16 | let jbProjectsFactory = await ethers.getContractFactory('JBProjects'); 17 | let jbProjectsStore = await jbProjectsFactory.deploy(jbOperatorStore.address); 18 | 19 | return { 20 | projectOwner, 21 | deployer, 22 | addrs, 23 | jbProjectsStore, 24 | }; 25 | } 26 | 27 | it(`Should create a project and emit Create`, async function () { 28 | const { projectOwner, deployer, jbProjectsStore } = await setup(); 29 | 30 | let tx = await jbProjectsStore 31 | .connect(deployer) 32 | .createFor(projectOwner.address, [METADATA_CID, METADATA_DOMAIN]); 33 | 34 | let storedMetadataCid = await jbProjectsStore 35 | .connect(deployer) 36 | .metadataContentOf(PROJECT_ID_1, METADATA_DOMAIN); 37 | 38 | await expect(storedMetadataCid).to.equal(METADATA_CID); 39 | 40 | await expect(tx) 41 | .to.emit(jbProjectsStore, 'Create') 42 | .withArgs( 43 | PROJECT_ID_1, 44 | projectOwner.address, 45 | [METADATA_CID, METADATA_DOMAIN], 46 | deployer.address, 47 | ); 48 | }); 49 | 50 | it(`Should create two projects and count to be 2 and emit Create`, async function () { 51 | const { projectOwner, deployer, jbProjectsStore } = await setup(); 52 | 53 | await jbProjectsStore 54 | .connect(deployer) 55 | .createFor(projectOwner.address, [METADATA_CID, METADATA_DOMAIN]); 56 | 57 | let tx = await jbProjectsStore 58 | .connect(deployer) 59 | .createFor(projectOwner.address, [METADATA_CID, METADATA_DOMAIN]); 60 | 61 | await expect(tx) 62 | .to.emit(jbProjectsStore, 'Create') 63 | .withArgs( 64 | PROJECT_ID_2, 65 | projectOwner.address, 66 | [METADATA_CID, METADATA_DOMAIN], 67 | deployer.address, 68 | ); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/jb_projects/set_metadata_of.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 7 | 8 | describe('JBProjects::setMetadataOf(...)', function () { 9 | const METADATA_CID = ''; 10 | const METADATA_DOMAIN = 1234; 11 | const METADATA_CID_2 = 'ipfs://randommetadatacidipsaddress'; 12 | const METADATA_DOMAIN_2 = 23435; 13 | const PROJECT_ID_1 = 1; 14 | 15 | let SET_METADATA_PERMISSION_INDEX; 16 | 17 | before(async function () { 18 | let jbOperationsFactory = await ethers.getContractFactory('JBOperations'); 19 | let jbOperations = await jbOperationsFactory.deploy(); 20 | 21 | SET_METADATA_PERMISSION_INDEX = await jbOperations.SET_METADATA(); 22 | }); 23 | 24 | async function setup() { 25 | let [deployer, projectOwner, ...addrs] = await ethers.getSigners(); 26 | 27 | let mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 28 | let jbProjectsFactory = await ethers.getContractFactory('JBProjects'); 29 | let jbProjectsStore = await jbProjectsFactory.deploy(mockJbOperatorStore.address); 30 | 31 | return { 32 | projectOwner, 33 | deployer, 34 | addrs, 35 | jbProjectsStore, 36 | mockJbOperatorStore, 37 | }; 38 | } 39 | 40 | it(`Should set MetadataCid on project by owner and emit SetMetadata`, async function () { 41 | const { projectOwner, deployer, jbProjectsStore } = await setup(); 42 | 43 | await jbProjectsStore 44 | .connect(deployer) 45 | .createFor(projectOwner.address, [METADATA_CID, METADATA_DOMAIN]); 46 | 47 | let tx = await jbProjectsStore 48 | .connect(projectOwner) 49 | .setMetadataOf(PROJECT_ID_1, [METADATA_CID_2, METADATA_DOMAIN_2]); 50 | 51 | let storedMetadataCid = await jbProjectsStore 52 | .connect(deployer) 53 | .metadataContentOf(PROJECT_ID_1, METADATA_DOMAIN_2); 54 | await expect(storedMetadataCid).to.equal(METADATA_CID_2); 55 | 56 | await expect(tx) 57 | .to.emit(jbProjectsStore, 'SetMetadata') 58 | .withArgs(PROJECT_ID_1, [METADATA_CID_2, METADATA_DOMAIN_2], projectOwner.address); 59 | }); 60 | 61 | it(`Should set MetadataCid on project if caller is not owner but has permission`, async function () { 62 | const { projectOwner, deployer, addrs, jbProjectsStore, mockJbOperatorStore } = await setup(); 63 | 64 | await jbProjectsStore 65 | .connect(deployer) 66 | .createFor(projectOwner.address, [METADATA_CID, METADATA_DOMAIN]); 67 | 68 | await mockJbOperatorStore.mock.hasPermission 69 | .withArgs(addrs[1].address, projectOwner.address, PROJECT_ID_1, SET_METADATA_PERMISSION_INDEX) 70 | .returns(true); 71 | 72 | await expect(jbProjectsStore.connect(addrs[1]).setMetadataOf(PROJECT_ID_1, METADATA_CID_2)).to 73 | .not.be.reverted; 74 | }); 75 | 76 | it(`Can't set MetadataCid on project if caller is not owner and doesn't have permission`, async function () { 77 | const { projectOwner, deployer, addrs, jbProjectsStore, mockJbOperatorStore } = await setup(); 78 | 79 | await jbProjectsStore 80 | .connect(deployer) 81 | .createFor(projectOwner.address, [METADATA_CID, METADATA_DOMAIN]); 82 | 83 | await mockJbOperatorStore.mock.hasPermission 84 | .withArgs(addrs[1].address, projectOwner.address, PROJECT_ID_1, SET_METADATA_PERMISSION_INDEX) 85 | .returns(false); 86 | 87 | await expect( 88 | jbProjectsStore 89 | .connect(addrs[1]) 90 | .setMetadataOf(PROJECT_ID_1, [METADATA_CID_2, METADATA_DOMAIN_2]), 91 | ).to.be.reverted; 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/jb_projects/set_token_uri_resolver.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 7 | import jbTokenUriResolver from '../../artifacts/contracts/interfaces/IJBTokenUriResolver.sol/IJBTokenUriResolver.json'; 8 | 9 | describe('JBProjects::setTokenUriResolver(...)', function () { 10 | async function setup() { 11 | let [deployer, caller] = await ethers.getSigners(); 12 | 13 | let mockJbTokenUriResolver = await deployMockContract(deployer, jbTokenUriResolver.abi); 14 | let mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 15 | 16 | let jbProjectsFactory = await ethers.getContractFactory('JBProjects'); 17 | let jbProjects = await jbProjectsFactory.deploy(mockJbOperatorStore.address); 18 | 19 | return { 20 | deployer, 21 | caller, 22 | jbProjects, 23 | mockJbTokenUriResolver, 24 | }; 25 | } 26 | 27 | it(`Should set the tokenUri resolver and emit event, if called by the contract owner`, async function () { 28 | const { deployer, jbProjects, mockJbTokenUriResolver } = await setup(); 29 | 30 | expect(await jbProjects.connect(deployer).setTokenUriResolver(mockJbTokenUriResolver.address)) 31 | .to.emit(jbProjects, 'SetTokenUriResolver') 32 | .withArgs(mockJbTokenUriResolver.address, deployer.address); 33 | 34 | expect(await jbProjects.tokenUriResolver()).to.equal(mockJbTokenUriResolver.address); 35 | }); 36 | 37 | it(`Can't set the tokenUri resolver if caller is not the contract owner`, async function () { 38 | const { caller, jbProjects, mockJbTokenUriResolver } = await setup(); 39 | 40 | await expect(jbProjects.connect(caller).setTokenUriResolver(mockJbTokenUriResolver.address)).to 41 | .be.reverted; 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/jb_projects/token_uri.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 7 | import jbTokenUriResolver from '../../artifacts/contracts/interfaces/IJBTokenUriResolver.sol/IJBTokenUriResolver.json'; 8 | 9 | describe('JBProjects::tokenURI(...)', function () { 10 | const TOKEN_URI = 'ipfs://randommetadatacidipsaddress'; 11 | const PROJECT_ID = 69; 12 | 13 | async function setup() { 14 | let [deployer] = await ethers.getSigners(); 15 | 16 | let mockJbTokenUriResolver = await deployMockContract(deployer, jbTokenUriResolver.abi); 17 | let mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 18 | 19 | let jbProjectsFactory = await ethers.getContractFactory('JBProjects'); 20 | let jbProjects = await jbProjectsFactory.deploy(mockJbOperatorStore.address); 21 | 22 | mockJbTokenUriResolver.mock.getUri.withArgs(PROJECT_ID).returns(TOKEN_URI); 23 | 24 | return { 25 | deployer, 26 | jbProjects, 27 | mockJbTokenUriResolver, 28 | }; 29 | } 30 | 31 | it(`Should return an empty string if the token URI resolver is not set`, async function () { 32 | const { jbProjects } = await setup(); 33 | 34 | expect(await jbProjects.tokenURI(PROJECT_ID)).to.equal(''); 35 | }); 36 | 37 | it(`Should return the correct URI if the token URI resolver is set`, async function () { 38 | const { deployer, jbProjects, mockJbTokenUriResolver } = await setup(); 39 | 40 | await jbProjects.connect(deployer).setTokenUriResolver(mockJbTokenUriResolver.address); 41 | 42 | expect(await jbProjects.tokenURI(PROJECT_ID)).to.equal(TOKEN_URI); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/jb_token/approve.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { deployJbToken } from '../helpers/utils'; 4 | 5 | describe('JBToken::approve(...)', function () { 6 | const PROJECT_ID = 10; 7 | const name = 'TestTokenDAO'; 8 | const symbol = 'TEST'; 9 | 10 | async function setup() { 11 | const [deployer, ...addrs] = await ethers.getSigners(); 12 | const jbToken = await deployJbToken(name, symbol); 13 | return { deployer, addrs, jbToken }; 14 | } 15 | 16 | it('Should approve and emit event if caller is owner', async function () { 17 | const { deployer, addrs, jbToken } = await setup(); 18 | const addr = addrs[1]; 19 | const numTokens = 3000; 20 | 21 | const mintTx = await jbToken 22 | .connect(deployer) 23 | ['approve(uint256,address,uint256)'](PROJECT_ID, addr.address, numTokens); 24 | 25 | await expect(mintTx) 26 | .to.emit(jbToken, 'Approval') 27 | .withArgs(deployer.address, addr.address, numTokens); 28 | 29 | // overloaded functions need to be called using the full function signature 30 | const allowance = await jbToken 31 | .connect(deployer) 32 | ['allowance(address,address)'](deployer.address, addr.address); 33 | expect(allowance).to.equal(numTokens); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/jb_token/burn.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { deployJbToken } from '../helpers/utils'; 4 | 5 | describe('JBToken::burn(...)', function () { 6 | const PROJECT_ID = 10; 7 | const name = 'TestTokenDAO'; 8 | const symbol = 'TEST'; 9 | const startingBalance = 3000; 10 | 11 | async function setup() { 12 | const [deployer, ...addrs] = await ethers.getSigners(); 13 | const jbToken = await deployJbToken(name, symbol); 14 | await jbToken.connect(deployer).mint(PROJECT_ID, addrs[1].address, startingBalance); 15 | return { deployer, addrs, jbToken }; 16 | } 17 | 18 | it('Should burn token and emit event if caller is owner', async function () { 19 | const { deployer, addrs, jbToken } = await setup(); 20 | const addr = addrs[1]; 21 | const numTokens = 5; 22 | const burnTx = await jbToken.connect(deployer).burn(PROJECT_ID, addr.address, numTokens); 23 | 24 | await expect(burnTx) 25 | .to.emit(jbToken, 'Transfer') 26 | .withArgs(addr.address, ethers.constants.AddressZero, numTokens); 27 | 28 | // overloaded functions need to be called using the full function signature 29 | const balance = await jbToken['balanceOf(address,uint256)'](addr.address, PROJECT_ID); 30 | expect(balance).to.equal(startingBalance - numTokens); 31 | }); 32 | 33 | it(`Can't burn tokens if caller isn't owner`, async function () { 34 | const { addrs, jbToken } = await setup(); 35 | const nonOwner = addrs[1]; 36 | await expect( 37 | jbToken.connect(nonOwner).burn(PROJECT_ID, nonOwner.address, 3000), 38 | ).to.be.revertedWith('Ownable: caller is not the owner'); 39 | }); 40 | 41 | it(`Can't burn tokens from zero address`, async function () { 42 | const { jbToken } = await setup(); 43 | await expect(jbToken.burn(PROJECT_ID, ethers.constants.AddressZero, 3000)).to.be.revertedWith( 44 | 'ERC20: burn from the zero address', 45 | ); 46 | }); 47 | 48 | it(`Can't burn tokens if burn amount exceeds balance`, async function () { 49 | const { addrs, jbToken } = await setup(); 50 | const addr = addrs[1]; 51 | const numTokens = 9001; 52 | await expect(jbToken.burn(PROJECT_ID, addr.address, numTokens)).to.be.revertedWith( 53 | 'ERC20: burn amount exceeds balance', 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/jb_token/decimals.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { deployJbToken } from '../helpers/utils'; 3 | 4 | describe('JBToken::decimals(...)', function () { 5 | it('Should have 18 decimals', async function () { 6 | const jbToken = await deployJbToken('asdf', 'asdf'); 7 | const decimals = await jbToken.decimals(); 8 | expect(decimals).to.equal(18); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/jb_token/mint.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { deployJbToken } from '../helpers/utils'; 4 | 5 | describe('JBToken::mint(...)', function () { 6 | const PROJECT_ID = 10; 7 | const name = 'TestTokenDAO'; 8 | const symbol = 'TEST'; 9 | 10 | async function setup() { 11 | const [deployer, ...addrs] = await ethers.getSigners(); 12 | const jbToken = await deployJbToken(name, symbol); 13 | return { deployer, addrs, jbToken }; 14 | } 15 | 16 | it('Should mint token and emit event if caller is owner', async function () { 17 | const { deployer, addrs, jbToken } = await setup(); 18 | const addr = addrs[1]; 19 | const numTokens = 3000; 20 | const mintTx = await jbToken.connect(deployer).mint(PROJECT_ID, addr.address, numTokens); 21 | 22 | await expect(mintTx) 23 | .to.emit(jbToken, 'Transfer') 24 | .withArgs(ethers.constants.AddressZero, addr.address, numTokens); 25 | 26 | // overloaded functions need to be called using the full function signature 27 | const balance = await jbToken['balanceOf(address,uint256)'](addr.address, PROJECT_ID); 28 | expect(balance).to.equal(numTokens); 29 | 30 | const supply = await jbToken['totalSupply(uint256)'](PROJECT_ID); 31 | expect(supply).to.equal(numTokens); 32 | }); 33 | 34 | it(`Can't mint tokens if caller isn't owner`, async function () { 35 | const { addrs, jbToken } = await setup(); 36 | const nonOwner = addrs[1]; 37 | await expect( 38 | jbToken.connect(nonOwner).mint(PROJECT_ID, nonOwner.address, 3000), 39 | ).to.be.revertedWith('Ownable: caller is not the owner'); 40 | }); 41 | 42 | it(`Can't mint tokens to zero address`, async function () { 43 | const { jbToken } = await setup(); 44 | await expect(jbToken.mint(PROJECT_ID, ethers.constants.AddressZero, 3000)).to.be.revertedWith( 45 | 'ERC20: mint to the zero address', 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/jb_token/transfer.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { deployJbToken } from '../helpers/utils'; 4 | 5 | describe('JBToken::transfer(...)', function () { 6 | const PROJECT_ID = 10; 7 | const name = 'TestTokenDAO'; 8 | const symbol = 'TEST'; 9 | const startingBalance = 3000; 10 | 11 | async function setup() { 12 | const [deployer, ...addrs] = await ethers.getSigners(); 13 | const jbToken = await deployJbToken(name, symbol); 14 | await jbToken.connect(deployer).mint(PROJECT_ID, addrs[1].address, startingBalance); 15 | return { deployer, addrs, jbToken }; 16 | } 17 | 18 | it('Should transfer token and emit event if caller is owner', async function () { 19 | const { addrs, jbToken } = await setup(); 20 | const numTokens = 5; 21 | const transferTx = await jbToken 22 | .connect(addrs[1]) 23 | ['transfer(uint256,address,uint256)'](PROJECT_ID, addrs[2].address, numTokens); 24 | 25 | await expect(transferTx) 26 | .to.emit(jbToken, 'Transfer') 27 | .withArgs(addrs[1].address, addrs[2].address, numTokens); 28 | 29 | // overloaded functions need to be called using the full function signature 30 | const balance = await jbToken['balanceOf(address,uint256)'](addrs[1].address, PROJECT_ID); 31 | expect(balance).to.equal(startingBalance - numTokens); 32 | }); 33 | 34 | it(`Can't transfer to zero address`, async function () { 35 | const { addrs, jbToken } = await setup(); 36 | const numTokens = startingBalance + 1; 37 | await expect( 38 | jbToken 39 | .connect(addrs[1]) 40 | ['transfer(uint256,address,uint256)'](PROJECT_ID, ethers.constants.AddressZero, numTokens), 41 | ).to.be.revertedWith('ERC20: transfer to the zero address'); 42 | }); 43 | 44 | it(`Can't transfer tokens if burn amount exceeds balance`, async function () { 45 | const { addrs, jbToken } = await setup(); 46 | const numTokens = startingBalance + 1; 47 | await expect( 48 | jbToken 49 | .connect(addrs[1]) 50 | ['transfer(uint256,address,uint256)'](PROJECT_ID, addrs[2].address, numTokens), 51 | ).to.be.revertedWith('ERC20: transfer amount exceeds balance'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/jb_token/transfer_from.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { deployJbToken } from '../helpers/utils'; 4 | 5 | describe('JBToken::transferFrom(...)', function () { 6 | const PROJECT_ID = 10; 7 | const name = 'TestTokenDAO'; 8 | const symbol = 'TEST'; 9 | const startingBalance = 3000; 10 | 11 | async function setup() { 12 | const [deployer, ...addrs] = await ethers.getSigners(); 13 | const jbToken = await deployJbToken(name, symbol); 14 | await jbToken.connect(deployer).mint(PROJECT_ID, addrs[1].address, startingBalance); 15 | return { deployer, addrs, jbToken }; 16 | } 17 | 18 | it('Should transfer token and emit event if caller is owner', async function () { 19 | const { addrs, jbToken } = await setup(); 20 | const numTokens = 5; 21 | await jbToken.connect(addrs[1])['approve(address,uint256)'](addrs[3].address, numTokens); 22 | const transferTx = await jbToken 23 | .connect(addrs[3]) 24 | ['transferFrom(uint256,address,address,uint256)']( 25 | PROJECT_ID, 26 | addrs[1].address, 27 | addrs[2].address, 28 | numTokens, 29 | ); 30 | 31 | await expect(transferTx) 32 | .to.emit(jbToken, 'Transfer') 33 | .withArgs(addrs[1].address, addrs[2].address, numTokens); 34 | 35 | // overloaded functions need to be called using the full function signature 36 | const balance = await jbToken['balanceOf(address,uint256)'](addrs[1].address, PROJECT_ID); 37 | expect(balance).to.equal(startingBalance - numTokens); 38 | }); 39 | 40 | it(`Can't transfer tokens if caller doesn't have approval`, async function () { 41 | const { addrs, jbToken } = await setup(); 42 | const numTokens = 5; 43 | 44 | await expect( 45 | jbToken 46 | .connect(addrs[1]) 47 | ['transferFrom(uint256,address,address,uint256)']( 48 | PROJECT_ID, 49 | addrs[1].address, 50 | addrs[2].address, 51 | numTokens, 52 | ), 53 | ).to.be.revertedWith('ERC20: transfer amount exceeds allowance'); 54 | }); 55 | 56 | it(`Can't transfer to zero address`, async function () { 57 | const { addrs, jbToken } = await setup(); 58 | const numTokens = startingBalance + 1; 59 | await jbToken.connect(addrs[1])['approve(address,uint256)'](addrs[3].address, numTokens); 60 | await expect( 61 | jbToken 62 | .connect(addrs[3]) 63 | ['transferFrom(uint256,address,address,uint256)']( 64 | PROJECT_ID, 65 | addrs[1].address, 66 | ethers.constants.AddressZero, 67 | numTokens, 68 | ), 69 | ).to.be.revertedWith('ERC20: transfer to the zero address'); 70 | }); 71 | 72 | it(`Can't transfer tokens if burn amount exceeds balance`, async function () { 73 | const { addrs, jbToken } = await setup(); 74 | const numTokens = startingBalance + 1; 75 | await jbToken.connect(addrs[1])[`approve(address,uint256)`](addrs[3].address, numTokens); 76 | await expect( 77 | jbToken 78 | .connect(addrs[3]) 79 | ['transferFrom(uint256,address,address,uint256)']( 80 | PROJECT_ID, 81 | addrs[1].address, 82 | addrs[2].address, 83 | numTokens, 84 | ), 85 | ).to.be.revertedWith('ERC20: transfer amount exceeds balance'); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/jb_token/transfer_ownership.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { deployJbToken } from '../helpers/utils'; 4 | 5 | describe('JBToken::transferOwnership(...)', function () { 6 | const name = 'TestTokenDAO'; 7 | const symbol = 'TEST'; 8 | const projectIdDoesntMatter = 123; 9 | 10 | async function setup() { 11 | const [deployer, ...addrs] = await ethers.getSigners(); 12 | const jbToken = await deployJbToken(name, symbol); 13 | return { deployer, addrs, jbToken }; 14 | } 15 | 16 | it('Should transfer ownership to another address if caller is owner', async function () { 17 | const { deployer, addrs, jbToken } = await setup(); 18 | const newAddr = addrs[0]; 19 | 20 | const transferOwnershipTx = await jbToken 21 | .connect(deployer) 22 | ['transferOwnership(uint256,address)'](projectIdDoesntMatter, newAddr.address); 23 | 24 | await expect(transferOwnershipTx) 25 | .to.emit(jbToken, 'OwnershipTransferred') 26 | .withArgs(deployer.address, newAddr.address); 27 | 28 | expect(await jbToken.owner()).to.equal(newAddr.address); 29 | }); 30 | 31 | it(`Can't transfer ownership if caller isn't owner`, async function () { 32 | const { addrs, jbToken } = await setup(); 33 | const newAddr = addrs[0]; 34 | const nonOwner = addrs[1]; 35 | await expect( 36 | jbToken 37 | .connect(nonOwner) 38 | ['transferOwnership(uint256,address)'](projectIdDoesntMatter, newAddr.address), 39 | ).to.be.revertedWith('Ownable: caller is not the owner'); 40 | }); 41 | 42 | it(`Can't set new owner to zero address`, async function () { 43 | const { jbToken } = await setup(); 44 | await expect( 45 | jbToken['transferOwnership(uint256,address)']( 46 | projectIdDoesntMatter, 47 | ethers.constants.AddressZero, 48 | ), 49 | ).to.be.revertedWith('Ownable: new owner is the zero address'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/jb_token_store/balance_of.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 7 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 8 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 9 | 10 | describe('JBTokenStore::balanceOf(...)', function () { 11 | const PROJECT_ID = 2; 12 | const TOKEN_NAME = 'TestTokenDAO'; 13 | const TOKEN_SYMBOL = 'TEST'; 14 | 15 | async function setup() { 16 | const [deployer, controller, newHolder] = await ethers.getSigners(); 17 | 18 | const mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 19 | const mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 20 | const mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); 21 | 22 | const jbTokenStoreFactory = await ethers.getContractFactory('JBTokenStore'); 23 | const jbTokenStore = await jbTokenStoreFactory.deploy( 24 | mockJbOperatorStore.address, 25 | mockJbProjects.address, 26 | mockJbDirectory.address, 27 | ); 28 | 29 | return { 30 | newHolder, 31 | controller, 32 | mockJbDirectory, 33 | jbTokenStore, 34 | }; 35 | } 36 | 37 | it('Should return token balance for holder', async function () { 38 | const { newHolder, controller, mockJbDirectory, jbTokenStore } = await setup(); 39 | 40 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 41 | 42 | await jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL); 43 | 44 | // Mint unclaimed tokens 45 | const numTokens = 20; 46 | await jbTokenStore 47 | .connect(controller) 48 | .mintFor(newHolder.address, PROJECT_ID, numTokens, /* preferClaimedTokens= */ false); 49 | 50 | expect(await jbTokenStore.balanceOf(newHolder.address, PROJECT_ID)).to.equal(numTokens); 51 | 52 | // Mint more claimed tokens 53 | await jbTokenStore 54 | .connect(controller) 55 | .mintFor(newHolder.address, PROJECT_ID, numTokens, /* preferClaimedTokens= */ true); 56 | 57 | expect(await jbTokenStore.balanceOf(newHolder.address, PROJECT_ID)).to.equal(numTokens * 2); 58 | }); 59 | 60 | it('Should return 0 if a token for projectId is not found', async function () { 61 | const { newHolder, jbTokenStore } = await setup(); 62 | 63 | expect(await jbTokenStore.balanceOf(newHolder.address, PROJECT_ID)).to.equal(0); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/jb_token_store/issue_for.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 7 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 8 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 9 | import jbToken from '../../artifacts/contracts/JBToken.sol/JBToken.json'; 10 | import { Contract } from 'ethers'; 11 | import errors from '../helpers/errors.json'; 12 | 13 | describe('JBTokenStore::issueFor(...)', function () { 14 | const PROJECT_ID = 2; 15 | const TOKEN_NAME = 'TestTokenDAO'; 16 | const TOKEN_SYMBOL = 'TEST'; 17 | 18 | async function setup() { 19 | const [deployer, controller] = await ethers.getSigners(); 20 | 21 | const mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 22 | const mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 23 | const mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); 24 | 25 | const jbTokenStoreFactory = await ethers.getContractFactory('JBTokenStore'); 26 | const jbTokenStore = await jbTokenStoreFactory.deploy( 27 | mockJbOperatorStore.address, 28 | mockJbProjects.address, 29 | mockJbDirectory.address, 30 | ); 31 | 32 | return { 33 | controller, 34 | mockJbDirectory, 35 | jbTokenStore, 36 | }; 37 | } 38 | 39 | it('Should issue tokens and emit event if caller is controller', async function () { 40 | const { controller, mockJbDirectory, jbTokenStore } = await setup(); 41 | 42 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 43 | 44 | const tx = await jbTokenStore 45 | .connect(controller) 46 | .issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL); 47 | 48 | const tokenAddr = await jbTokenStore.connect(controller).tokenOf(PROJECT_ID); 49 | const token = new Contract(tokenAddr, jbToken.abi); 50 | 51 | expect(await jbTokenStore.projectOf(tokenAddr)).to.equal(PROJECT_ID); 52 | 53 | expect(await token.connect(controller).name()).to.equal(TOKEN_NAME); 54 | expect(await token.connect(controller).symbol()).to.equal(TOKEN_SYMBOL); 55 | 56 | await expect(tx) 57 | .to.emit(jbTokenStore, 'Issue') 58 | .withArgs(PROJECT_ID, tokenAddr, TOKEN_NAME, TOKEN_SYMBOL, controller.address); 59 | }); 60 | 61 | it(`Can't issue tokens if name is empty`, async function () { 62 | const { controller, mockJbDirectory, jbTokenStore } = await setup(); 63 | 64 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 65 | 66 | const name = ''; 67 | await expect( 68 | jbTokenStore.connect(controller).issueFor(PROJECT_ID, name, TOKEN_SYMBOL), 69 | ).to.be.revertedWith(errors.EMPTY_NAME); 70 | }); 71 | 72 | it(`Can't issue tokens if symbol is empty`, async function () { 73 | const { controller, mockJbDirectory, jbTokenStore } = await setup(); 74 | 75 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 76 | 77 | const symbol = ''; 78 | await expect( 79 | jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, symbol), 80 | ).to.be.revertedWith(errors.EMPTY_SYMBOL); 81 | }); 82 | 83 | it(`Can't issue tokens if already issued`, async function () { 84 | const { controller, mockJbDirectory, jbTokenStore } = await setup(); 85 | 86 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 87 | 88 | // First issuance should succeed; second should fail. 89 | await expect(jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL)).to 90 | .not.be.reverted; 91 | await expect( 92 | jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL), 93 | ).to.be.revertedWith(errors.PROJECT_ALREADY_HAS_TOKEN); 94 | }); 95 | 96 | it(`Can't issue tokens if caller does not have permission`, async function () { 97 | const { controller, mockJbDirectory, jbTokenStore } = await setup(); 98 | 99 | // Return a random controller address. 100 | await mockJbDirectory.mock.controllerOf 101 | .withArgs(PROJECT_ID) 102 | .returns(ethers.Wallet.createRandom().address); 103 | 104 | await expect( 105 | jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL), 106 | ).to.be.revertedWith(errors.CONTROLLER_UNAUTHORIZED); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/jb_token_store/mint_for.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 7 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 8 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 9 | import errors from '../helpers/errors.json'; 10 | 11 | describe('JBTokenStore::mintFor(...)', function () { 12 | const PROJECT_ID = 2; 13 | const TOKEN_NAME = 'TestTokenDAO'; 14 | const TOKEN_SYMBOL = 'TEST'; 15 | 16 | async function setup() { 17 | const [deployer, controller, newHolder] = await ethers.getSigners(); 18 | 19 | const mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 20 | const mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 21 | const mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); 22 | 23 | const jbTokenStoreFactory = await ethers.getContractFactory('JBTokenStore'); 24 | const jbTokenStore = await jbTokenStoreFactory.deploy( 25 | mockJbOperatorStore.address, 26 | mockJbProjects.address, 27 | mockJbDirectory.address, 28 | ); 29 | 30 | return { 31 | controller, 32 | newHolder, 33 | mockJbDirectory, 34 | jbTokenStore, 35 | }; 36 | } 37 | 38 | it('Should mint claimed tokens and emit event if caller is controller', async function () { 39 | const { controller, newHolder, mockJbDirectory, jbTokenStore } = await setup(); 40 | 41 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 42 | 43 | await jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL); 44 | 45 | // Mint more claimed tokens 46 | const numTokens = 20; 47 | const mintForTx = await jbTokenStore 48 | .connect(controller) 49 | .mintFor(newHolder.address, PROJECT_ID, numTokens, /* preferClaimedTokens= */ true); 50 | 51 | expect(await jbTokenStore.unclaimedBalanceOf(newHolder.address, PROJECT_ID)).to.equal(0); 52 | expect(await jbTokenStore.balanceOf(newHolder.address, PROJECT_ID)).to.equal(numTokens); 53 | 54 | await expect(mintForTx) 55 | .to.emit(jbTokenStore, 'Mint') 56 | .withArgs( 57 | newHolder.address, 58 | PROJECT_ID, 59 | numTokens, 60 | /* shouldClaimTokens= */ true, 61 | /* preferClaimedTokens= */ true, 62 | controller.address, 63 | ); 64 | }); 65 | 66 | it('Should mint unclaimed tokens and emit event if caller is controller', async function () { 67 | const { controller, newHolder, mockJbDirectory, jbTokenStore } = await setup(); 68 | 69 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 70 | 71 | await jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL); 72 | 73 | // Mint more unclaimed tokens 74 | const numTokens = 20; 75 | const mintForTx = await jbTokenStore 76 | .connect(controller) 77 | .mintFor(newHolder.address, PROJECT_ID, numTokens, /* preferClaimedTokens= */ false); 78 | 79 | expect(await jbTokenStore.unclaimedBalanceOf(newHolder.address, PROJECT_ID)).to.equal( 80 | numTokens, 81 | ); 82 | expect(await jbTokenStore.balanceOf(newHolder.address, PROJECT_ID)).to.equal(numTokens); 83 | 84 | await expect(mintForTx) 85 | .to.emit(jbTokenStore, 'Mint') 86 | .withArgs( 87 | newHolder.address, 88 | PROJECT_ID, 89 | numTokens, 90 | /* shouldClaimTokens= */ false, 91 | /* preferClaimedTokens= */ false, 92 | controller.address, 93 | ); 94 | }); 95 | 96 | it(`Can't mint tokens if caller does not have permission`, async function () { 97 | const { newHolder, mockJbDirectory, jbTokenStore } = await setup(); 98 | 99 | // Return a random controller address. 100 | await mockJbDirectory.mock.controllerOf 101 | .withArgs(PROJECT_ID) 102 | .returns(ethers.Wallet.createRandom().address); 103 | 104 | await expect( 105 | jbTokenStore.mintFor( 106 | newHolder.address, 107 | PROJECT_ID, 108 | /* amount= */ 1, 109 | /* preferClaimedTokens= */ true, 110 | ), 111 | ).to.be.revertedWith(errors.CONTROLLER_UNAUTHORIZED); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/jb_token_store/total_supply_of.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | 4 | import { deployMockContract } from '@ethereum-waffle/mock-contract'; 5 | 6 | import jbDirectory from '../../artifacts/contracts/JBDirectory.sol/JBDirectory.json'; 7 | import jbOperatoreStore from '../../artifacts/contracts/JBOperatorStore.sol/JBOperatorStore.json'; 8 | import jbProjects from '../../artifacts/contracts/JBProjects.sol/JBProjects.json'; 9 | 10 | describe('JBTokenStore::totalySupplyOf(...)', function () { 11 | const PROJECT_ID = 2; 12 | const TOKEN_NAME = 'TestTokenDAO'; 13 | const TOKEN_SYMBOL = 'TEST'; 14 | 15 | async function setup() { 16 | const [deployer, ...addrs] = await ethers.getSigners(); 17 | 18 | const mockJbOperatorStore = await deployMockContract(deployer, jbOperatoreStore.abi); 19 | const mockJbProjects = await deployMockContract(deployer, jbProjects.abi); 20 | const mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); 21 | 22 | const jbTokenStoreFactory = await ethers.getContractFactory('JBTokenStore'); 23 | const jbTokenStore = await jbTokenStoreFactory.deploy( 24 | mockJbOperatorStore.address, 25 | mockJbProjects.address, 26 | mockJbDirectory.address, 27 | ); 28 | 29 | return { 30 | addrs, 31 | mockJbDirectory, 32 | jbTokenStore, 33 | }; 34 | } 35 | 36 | it('Should return total supply of tokens for given projectId', async function () { 37 | const { addrs, mockJbDirectory, jbTokenStore } = await setup(); 38 | const controller = addrs[0]; 39 | 40 | await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); 41 | 42 | await jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL); 43 | 44 | // Mint unclaimed tokens 45 | const newHolder = addrs[1]; 46 | const numTokens = 20; 47 | await jbTokenStore 48 | .connect(controller) 49 | .mintFor(newHolder.address, PROJECT_ID, numTokens, /* _preferClaimedTokens= */ false); 50 | 51 | // Mint claimed tokens for another holder 52 | const anotherHolder = addrs[2]; 53 | await jbTokenStore 54 | .connect(controller) 55 | .mintFor(anotherHolder.address, PROJECT_ID, numTokens, /* preferClaimedTokens= */ true); 56 | 57 | expect(await jbTokenStore.totalSupplyOf(PROJECT_ID)).to.equal(numTokens * 2); 58 | }); 59 | 60 | it('Should return 0 if a token for projectId is not found', async function () { 61 | const { jbTokenStore } = await setup(); 62 | 63 | expect(await jbTokenStore.totalSupplyOf(PROJECT_ID)).to.equal(0); 64 | }); 65 | }); 66 | --------------------------------------------------------------------------------