@croptop/core-v6 0.0.32 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/RISKS.md CHANGED
@@ -22,6 +22,7 @@ This file focuses on the publishing, fee-routing, and hook-composition risks tha
22
22
  - **Trusted forwarder.** ERC-2771 `_msgSender()` is trusted in both CTPublisher and CTDeployer for permission checks, allowlist validation, and payment routing. A compromised forwarder can post as any allowed address, deploy projects as any owner, and redirect payments.
23
23
  - **CTDeployer as permanent data hook proxy.** `CTDeployer` sets itself as the data hook for projects it deploys. `dataHookOf[projectId]` is set once during `deployProjectFor` and has no setter to update it. If the underlying data hook needs to change, there is no mechanism to do so without redeploying.
24
24
  - **Sucker registry.** `CTDeployer.beforeCashOutRecordedWith` trusts `SUCKER_REGISTRY.isSuckerOf()` for 0% tax cashouts, same risk as the omnichain deployer.
25
+ - **Sucker deployment is intended to be fail-open at launch time.** `deployProjectFor` calls `SUCKER_REGISTRY.deploySuckersFor` only when a non-zero deployment salt is configured, and the current deployment path is documented as allowing launch to continue on chains where the configured sucker deployer cascade cannot complete. Operators should not assume a successful Croptop launch implies omnichain support is live.
25
26
  - **CTProjectOwner as burn target.** Projects transferred to `CTProjectOwner` grant `ADJUST_721_TIERS` to `PUBLISHER`. The project NFT cannot be recovered -- this is intentional but irreversible.
26
27
  - **JBDirectory / Terminal resolution.** `CTPublisher.mintFrom` resolves terminals via `DIRECTORY.primaryTerminalOf()`. A compromised directory could redirect payment and fee flows.
27
28
  - **721 hook store.** `_setupPosts` calls `hook.STORE().tierOf()` and `hook.STORE().isTierRemoved()`. The store is trusted to return accurate tier data. A malicious hook returning a fake store can report manipulated prices, supply limits, and removal status, causing `_setupPosts` to miscalculate `totalPrice` or skip duplicate detection.
@@ -59,6 +60,7 @@ This file focuses on the publishing, fee-routing, and hook-composition risks tha
59
60
 
60
61
  - **CTDeployer forwards pay/cashout calls to `dataHookOf` with null check.** `beforePayRecordedWith` and `beforeCashOutRecordedWith` check for a null `dataHookOf` and return defaults (context weight, empty specs) instead of reverting. If a non-null data hook reverts, payments/cashouts for the project are still blocked.
61
62
  - **No mechanism for hook migration.** `dataHookOf` is written once in `deployProjectFor` and never updated. If the data hook becomes compromised, there is no governance path to replace it without deploying a new project.
63
+ - **Sucker support can be absent even when deployment requested it.** Launch does not treat successful sucker setup as part of the Croptop app's core publish flow. A project can come online with the publisher, hook, and main terminal flow active while no suckers were actually deployed yet. Monitoring and deployment tooling should verify the returned sucker set explicitly instead of inferring success from project launch.
62
64
  - **Tier ID prediction.** `_setupPosts` predicts new tier IDs as `maxTierIdOf(hook) + 1 + i`. If another transaction adds tiers between `maxTierIdOf` read and `adjustTiers` execution, tier IDs shift and the wrong tiers are minted. This is a race condition in concurrent posting.
63
65
  - **CTProjectOwner accepts any project NFT.** `onERC721Received` grants `ADJUST_721_TIERS` to `PUBLISHER` for whatever tokenId is received. If a non-Croptop project is accidentally transferred to `CTProjectOwner`, the publisher gains tier adjustment permission for it.
64
66
  - **Fee payment destination.** Fees are routed to `FEE_PROJECT_ID` via its primary terminal. If the fee project changes its terminal or token acceptance incompatibly, `mintFrom` attempts to refund the fee to `_msgSender()`. If the caller cannot receive ETH, the mint reverts.
package/SKILLS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## Use This File For
4
4
 
5
5
  - Use this file when the task touches Croptop publishing, project deployment, data-hook forwarding, fee routing, or burn-locked ownership behavior.
6
- - Start here, then jump into the publisher, deployer, or owner contract depending on which path the user is actually asking about.
6
+ - Start here, then decide whether the issue is posting-policy validation, tier reuse/content identity, deployer-packaged project shape, or burn-locked ownership. Those concerns interact, but they are not the same subsystem.
7
7
 
8
8
  ## Read This Next
9
9
 
@@ -13,7 +13,9 @@
13
13
  | Publishing and metadata behavior | [`src/CTPublisher.sol`](./src/CTPublisher.sol) |
14
14
  | Deployment and fee-project wiring | [`src/CTDeployer.sol`](./src/CTDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol), [`script/ConfigureFeeProject.s.sol`](./script/ConfigureFeeProject.s.sol) |
15
15
  | Ownership burn-lock behavior | [`src/CTProjectOwner.sol`](./src/CTProjectOwner.sol) |
16
- | Regression, attack, or fork coverage | [`test/regression/`](./test/regression/), [`test/fork/`](./test/fork/), [`test/`](./test/) |
16
+ | Runtime and operational invariants | [`references/runtime.md`](./references/runtime.md), [`references/operations.md`](./references/operations.md) |
17
+ | Publishing, metadata, and attack coverage | [`test/CTPublisher.t.sol`](./test/CTPublisher.t.sol), [`test/Test_MetadataGeneration.t.sol`](./test/Test_MetadataGeneration.t.sol), [`test/CroptopAttacks.t.sol`](./test/CroptopAttacks.t.sol) |
18
+ | Deployment, ownership, and fork coverage | [`test/CTDeployer.t.sol`](./test/CTDeployer.t.sol), [`test/CTProjectOwner.t.sol`](./test/CTProjectOwner.t.sol), [`test/ClaimCollectionOwnership.t.sol`](./test/ClaimCollectionOwnership.t.sol), [`test/Fork.t.sol`](./test/Fork.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol) |
17
19
 
18
20
  ## Repo Map
19
21
 
@@ -37,5 +39,8 @@ Permissioned publishing layer for Juicebox 721 projects. Project owners define p
37
39
 
38
40
  - Start in [`src/CTPublisher.sol`](./src/CTPublisher.sol) for posting-rule and fee behavior, but check [`src/CTDeployer.sol`](./src/CTDeployer.sol) when the bug might come from project shape or hook forwarding.
39
41
  - Treat posting criteria, fee routing, and duplicate-content handling as treasury-sensitive and product-sensitive at the same time.
42
+ - Category policy is part of the product surface. Changes to allowed addresses, supply bounds, or split caps alter what can be published, not just how it is paid for.
40
43
  - If the task mentions project immutability or admin recovery, inspect [`src/CTProjectOwner.sol`](./src/CTProjectOwner.sol) before changing deployer or publisher code.
44
+ - Metadata bugs can be publishing bugs, resolver-shape bugs, or duplicate-content bugs. Check all three before assuming a string-formatting issue.
45
+ - Duplicate-post and tier-reuse behavior are first-class runtime semantics. Do not treat them like cacheable convenience logic.
41
46
  - When a bug looks like generic 721 issuance, confirm it is not actually in `nana-721-hook-v6`.
package/USER_JOURNEYS.md CHANGED
@@ -1,67 +1,118 @@
1
1
  # User Journeys
2
2
 
3
- ## Who This Repo Serves
3
+ ## Repo Purpose
4
4
 
5
- - project owners turning a Juicebox 721 project into a publishing marketplace
6
- - publishers creating or reusing NFT tiers as posts
7
- - operators locking project administration into Croptop-specific ownership patterns
5
+ This repo turns a Juicebox 721 project into a permissioned publishing system.
6
+ It owns post validation, Croptop fee routing, and the deployment packaging that turns a project into a Croptop-managed
7
+ publisher. It does not own the base terminal accounting or the underlying 721 tier mechanics it wraps.
8
+
9
+ ## Primary Actors
10
+
11
+ - project owners creating a Croptop publishing surface
12
+ - publishers minting posts into an existing Croptop project
13
+ - auditors reviewing fee routing, posting policy, and owner-lock semantics
14
+
15
+ ## Key Surfaces
16
+
17
+ - `CTPublisher`: validates posts, adjusts tiers, mints the first copy, and routes Croptop fees
18
+ - `CTDeployer`: launches a Croptop-shaped project and can compose omnichain deployment
19
+ - `CTProjectOwner`: owner helper that can burn-lock administration into Croptop
20
+ - `mintFrom(...)`: main publishing entrypoint for new content
8
21
 
9
22
  ## Journey 1: Turn A Project Into A Croptop Publisher
10
23
 
11
- **Starting state:** a project already exists or is about to launch, and the owner wants category-level posting rules.
24
+ **Actor:** project owner.
25
+
26
+ **Intent:** install Croptop publishing policy on a project.
27
+
28
+ **Preconditions**
29
+ - the project already exists or will be launched through `CTDeployer`
30
+ - the owner has chosen category rules and the expected 721 hook shape
31
+
32
+ **Main Flow**
33
+ 1. Configure category-level constraints such as price floor, supply, splits, and allowlists.
34
+ 2. Install or verify the expected 721 hook setup.
35
+ 3. Route publishing through Croptop so future posts are policy-checked instead of free-form tier edits.
12
36
 
13
- **Success:** Croptop posting criteria are installed and future posts must satisfy them.
37
+ **Failure Modes**
38
+ - category rules do not match the intended publishing product
39
+ - teams assume Croptop replaces the need to audit the underlying 721 hook
14
40
 
15
- **Flow**
16
- 1. Configure category-level posting constraints such as price floor, supply bounds, split limits, and optional allowlists.
17
- 2. Install or verify the 721 hook shape the project expects.
18
- 3. Route the project through Croptop's publisher logic so future post creation is policy-checked instead of free-form tier editing.
41
+ **Postconditions**
42
+ - the project now routes publishing through Croptop policy rather than direct free-form tier creation
19
43
 
20
44
  ## Journey 2: Publish Content Into An Existing Croptop Project
21
45
 
22
- **Starting state:** a publisher has a post that satisfies the target project's posting rules.
46
+ **Actor:** publisher.
23
47
 
24
- **Success:** the post becomes a valid 721 tier and the first mint settles correctly.
48
+ **Intent:** publish one post into a Croptop project and mint the first copy.
25
49
 
26
- **Flow**
27
- 1. The publisher calls `mintFrom(...)` or the equivalent publishing surface with the content URI and pricing data.
28
- 2. `CTPublisher` checks the post against category rules and fee policy.
29
- 3. It creates or reuses the underlying 721 tier, mints the first copy, and routes both project revenue and the Croptop fee. If the fee terminal is unavailable, the fee is refunded to `_msgSender()` instead.
50
+ **Preconditions**
51
+ - the post satisfies the target project's category policy
52
+ - the caller can receive ETH if the fee refund fallback is needed
53
+ - duplicate-content and stale-tier implications are understood
30
54
 
31
- **Failure cases that matter:** duplicate URIs, split configurations that evade fees, stale tier mappings, publisher inputs that satisfy the 721 hook but violate Croptop's stricter publishing rules, and callers that cannot receive ETH when a fee refund fallback is needed.
55
+ **Main Flow**
56
+ 1. Call `mintFrom(...)` with the content URI and pricing data.
57
+ 2. `CTPublisher` validates the post against category and fee policy.
58
+ 3. It creates or reuses the underlying tier, mints the first copy, and routes project revenue plus the Croptop fee.
59
+
60
+ **Failure Modes**
61
+ - duplicate URIs or stale tier mappings
62
+ - publisher inputs satisfy the base 721 hook but violate Croptop's stricter rules
63
+ - the fee terminal rejects the fee payment and `_msgSender()` cannot receive the refund
64
+
65
+ **Postconditions**
66
+ - the post is minted or reused as a tier under Croptop policy and the fee path is accounted for
32
67
 
33
68
  ## Journey 3: Launch A New Croptop Project End To End
34
69
 
35
- **Starting state:** the product wants a fresh project that already has Croptop deployment choices baked in.
70
+ **Actor:** product team or deployer.
71
+
72
+ **Intent:** launch a project already wired for Croptop publishing.
36
73
 
37
- **Success:** one deployment flow launches the project, wires the 721 hook, and installs the initial posting rules.
74
+ **Preconditions**
75
+ - the team has project config, posting rules, and any omnichain requirements ready
76
+ - the correct `FEE_PROJECT_ID` is known for deployment
38
77
 
39
- **Flow**
40
- 1. Use `CTDeployer` with project config, posting rules, and any omnichain deployment config.
41
- 2. The deployer launches the Juicebox project, configures the Croptop-specific owner model, and wires in publisher behavior.
42
- 3. The project is ready for publishers without a manual post-launch setup phase.
78
+ **Main Flow**
79
+ 1. Use `CTDeployer` with project config, posting rules, and optional omnichain config.
80
+ 2. The deployer launches the project, configures Croptop ownership assumptions, and wires publisher behavior.
81
+ 3. The resulting project is ready for publishers without a manual post-launch setup gap.
82
+
83
+ **Failure Modes**
84
+ - the fee project is misconfigured or omitted
85
+ - teams treat `CTDeployer` as packaging only and miss its policy implications
86
+
87
+ **Postconditions**
88
+ - the resulting project is ready for Croptop publishers without a post-launch wiring gap
43
89
 
44
90
  ## Journey 4: Lock Administration Into Croptop's Owner Surface
45
91
 
46
- **Starting state:** the project should continue operating through Croptop's policy surface instead of ordinary project-owner discretion.
92
+ **Actor:** project owner.
93
+
94
+ **Intent:** keep governance inside Croptop's constrained owner surface.
47
95
 
48
- **Success:** the project's admin path is burn-locked or otherwise routed through `CTProjectOwner`.
96
+ **Preconditions**
97
+ - the owner wants irreversible product-shaping constraints, not ordinary owner flexibility
49
98
 
50
- **Flow**
51
- 1. Transfer or configure ownership so Croptop's owner helper controls the relevant admin surface.
99
+ **Main Flow**
100
+ 1. Transfer or configure ownership so `CTProjectOwner` controls the relevant admin surface.
52
101
  2. Restrict future edits to the paths Croptop intentionally exposes.
53
- 3. Accept that this is a product-shaping choice, not a cosmetic deployment detail.
102
+ 3. Accept that this is an ownership-model decision, not cosmetic packaging.
54
103
 
55
- ## Journey 5: Support Cross-Chain Payments Through Data Hooks
104
+ **Failure Modes**
105
+ - teams burn-lock before validating the publishing policy in production-like conditions
106
+ - reviewers miss that prior owner discretion no longer exists directly
56
107
 
57
- **Starting state:** a sucker pays the Croptop project on behalf of a remote user via `payRemote`, and `CTDeployer.beforePayRecordedWith` needs to forward the correct beneficiary to downstream hooks.
108
+ **Postconditions**
109
+ - future administration is constrained to the Croptop owner surface instead of ordinary owner discretion
58
110
 
59
- **Success:** downstream data hooks see the real remote user so any hook-specific accounting accrues to the right person.
111
+ ## Trust Boundaries
60
112
 
61
- **Flow**
62
- 1. The sucker calls `terminal.pay()` with relay-beneficiary metadata.
63
- 2. `CTDeployer.beforePayRecordedWith()` resolves the relay beneficiary when the payer is a registered sucker.
64
- 3. The swapped beneficiary is forwarded to the downstream data hook.
113
+ - this repo is trusted for publishing policy and fee routing
114
+ - the underlying 721 hook remains trusted for tier issuance and lower-level NFT accounting
115
+ - Croptop fee behavior depends on the fee project and its terminal remaining correctly configured
65
116
 
66
117
  ## Hand-Offs
67
118
 
package/foundry.toml CHANGED
@@ -16,6 +16,8 @@ fail_on_revert = false
16
16
  [rpc_endpoints]
17
17
  ethereum = "${RPC_ETHEREUM_MAINNET}"
18
18
 
19
+ [lint]
20
+ exclude_lints = ["pascal-case-struct", "mixed-case-variable"]
19
21
  [fmt]
20
22
  number_underscore = "thousands"
21
23
  multiline_func_header = "all"
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
1
  {
2
- "name": "@croptop/core-v6",
3
- "version": "0.0.32",
4
- "license": "MIT",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/mejango/croptop-core-v6"
8
- },
9
- "scripts": {
10
- "test": "forge test",
11
- "coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
12
- "deploy:mainnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
13
- "deploy:mainnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks mainnets",
14
- "deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
15
- "deploy:testnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks testnets",
16
- "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-v5'"
17
- },
18
- "dependencies": {
19
- "@bananapus/721-hook-v6": "^0.0.33",
20
- "@bananapus/buyback-hook-v6": "^0.0.27",
21
- "@bananapus/core-v6": "^0.0.34",
22
- "@bananapus/ownable-v6": "^0.0.17",
23
- "@bananapus/permission-ids-v6": "^0.0.17",
24
- "@bananapus/router-terminal-v6": "^0.0.26",
25
- "@bananapus/suckers-v6": "^0.0.25",
26
- "@openzeppelin/contracts": "^5.6.1"
27
- },
28
- "devDependencies": {
29
- "@bananapus/address-registry-v6": "^0.0.17",
30
- "@rev-net/core-v6": "^0.0.30",
31
- "@sphinx-labs/plugins": "^0.33.3"
32
- }
2
+ "name": "@croptop/core-v6",
3
+ "version": "0.0.34",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/mejango/croptop-core-v6"
8
+ },
9
+ "scripts": {
10
+ "test": "forge test",
11
+ "coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
12
+ "deploy:mainnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
13
+ "deploy:mainnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks mainnets",
14
+ "deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
15
+ "deploy:testnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks testnets",
16
+ "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-v5'"
17
+ },
18
+ "dependencies": {
19
+ "@bananapus/721-hook-v6": "^0.0.35",
20
+ "@bananapus/buyback-hook-v6": "^0.0.27",
21
+ "@bananapus/core-v6": "^0.0.34",
22
+ "@bananapus/ownable-v6": "^0.0.17",
23
+ "@bananapus/permission-ids-v6": "^0.0.17",
24
+ "@bananapus/router-terminal-v6": "^0.0.26",
25
+ "@bananapus/suckers-v6": "^0.0.25",
26
+ "@openzeppelin/contracts": "^5.6.1"
27
+ },
28
+ "devDependencies": {
29
+ "@bananapus/address-registry-v6": "^0.0.17",
30
+ "@rev-net/core-v6": "^0.0.32",
31
+ "@sphinx-labs/plugins": "^0.33.3"
32
+ }
33
33
  }
@@ -21,5 +21,5 @@
21
21
 
22
22
  ## Useful Proof Points
23
23
 
24
- - [`test/fork/`](../test/fork/) when deployment shape matters.
25
- - [`script/helpers/`](../script/helpers/) if the issue is really script/config assembly.
24
+ - [`test/Fork.t.sol`](../test/Fork.t.sol) when deployment shape matters.
25
+ - [`script/ConfigureFeeProject.s.sol`](../script/ConfigureFeeProject.s.sol) if the issue is really script/config assembly.
@@ -22,6 +22,6 @@
22
22
 
23
23
  ## Tests To Trust First
24
24
 
25
- - [`test/regression/`](../test/regression/) for pinned content and tier edge cases.
26
- - [`test/fork/`](../test/fork/) for live integration assumptions.
27
- - [`test/`](../test/) broadly when the issue could be in publisher or deployer behavior rather than one isolated function.
25
+ - [`test/CTPublisher.t.sol`](../test/CTPublisher.t.sol) and [`test/Test_MetadataGeneration.t.sol`](../test/Test_MetadataGeneration.t.sol) for content and metadata behavior.
26
+ - [`test/CTDeployer.t.sol`](../test/CTDeployer.t.sol) and [`test/Fork.t.sol`](../test/Fork.t.sol) for live deployment assumptions.
27
+ - [`test/CroptopAttacks.t.sol`](../test/CroptopAttacks.t.sol) and [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) when the issue could be in publisher or deployer behavior rather than one isolated function.
@@ -1,9 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.28;
3
3
 
4
- import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
5
- import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
6
- import {Context} from "@openzeppelin/contracts/utils/Context.sol";
7
4
  import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
8
5
  import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
9
6
  import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
@@ -28,7 +25,9 @@ import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.so
28
25
  import {JBOwnable} from "@bananapus/ownable-v6/src/JBOwnable.sol";
29
26
  import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
30
27
  import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
31
- import {JBRelayBeneficiary} from "@bananapus/suckers-v6/src/libraries/JBRelayBeneficiary.sol";
28
+ import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
29
+ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
30
+ import {Context} from "@openzeppelin/contracts/utils/Context.sol";
32
31
 
33
32
  import {ICTDeployer} from "./interfaces/ICTDeployer.sol";
34
33
  import {ICTPublisher} from "./interfaces/ICTPublisher.sol";
@@ -118,127 +117,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
118
117
  PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
119
118
  }
120
119
 
121
- //*********************************************************************//
122
- // ------------------------- external views -------------------------- //
123
- //*********************************************************************//
124
-
125
- /// @notice Allow cash outs from suckers without a tax.
126
- /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a cash out.
127
- /// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
128
- /// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
129
- /// out.
130
- /// @return cashOutCount The number of project tokens that are cashed out.
131
- /// @return totalSupply The total project token supply.
132
- /// @return hookSpecifications The amount of funds and the data to send to cash out hooks (this contract).
133
- function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
134
- external
135
- view
136
- override
137
- returns (
138
- uint256 cashOutTaxRate,
139
- uint256 cashOutCount,
140
- uint256 totalSupply,
141
- JBCashOutHookSpecification[] memory hookSpecifications
142
- )
143
- {
144
- // If the cash out is from a sucker, return the full cash out amount without taxes or fees.
145
- if (SUCKER_REGISTRY.isSuckerOf({projectId: context.projectId, addr: context.holder})) {
146
- return (0, context.cashOutCount, context.totalSupply, hookSpecifications);
147
- }
148
-
149
- // If the ruleset has a data hook, forward the call to the datahook.
150
- IJBRulesetDataHook hook = dataHookOf[context.projectId];
151
- if (address(hook) == address(0)) {
152
- return (context.cashOutTaxRate, context.cashOutCount, context.totalSupply, hookSpecifications);
153
- }
154
- // slither-disable-next-line unused-return
155
- return hook.beforeCashOutRecordedWith(context);
156
- }
157
-
158
- /// @notice Forward the call to the original data hook.
159
- /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a payment.
160
- /// @param context Standard Juicebox payment context. See `JBBeforePayRecordedContext`.
161
- /// @return weight The weight which project tokens are minted relative to. This can be used to customize how many
162
- /// tokens get minted by a payment.
163
- /// @return hookSpecifications Amounts (out of what's being paid in) to be sent to pay hooks instead of being paid
164
- /// into the project. Useful for automatically routing funds from a treasury as payments come in.
165
- function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
166
- external
167
- view
168
- override
169
- returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
170
- {
171
- // Forward the call to the data hook.
172
- IJBRulesetDataHook hook = dataHookOf[context.projectId];
173
- if (address(hook) == address(0)) {
174
- return (context.weight, hookSpecifications);
175
- }
176
-
177
- // Resolve the relay beneficiary — if the payer is a sucker with relay metadata,
178
- // swap the beneficiary so downstream hooks see the real user.
179
- address effectiveBeneficiary = JBRelayBeneficiary.resolve({
180
- payer: context.payer,
181
- beneficiary: context.beneficiary,
182
- projectId: context.projectId,
183
- metadata: context.metadata,
184
- registry: SUCKER_REGISTRY
185
- });
186
-
187
- // If the beneficiary was swapped, create a memory copy with the new beneficiary.
188
- if (effectiveBeneficiary != context.beneficiary) {
189
- JBBeforePayRecordedContext memory hookContext = context;
190
- hookContext.beneficiary = effectiveBeneficiary;
191
- return hook.beforePayRecordedWith(hookContext);
192
- }
193
-
194
- // slither-disable-next-line unused-return
195
- return hook.beforePayRecordedWith(context);
196
- }
197
-
198
- /// @notice A flag indicating whether an address has permission to mint a project's tokens on-demand.
199
- /// @dev A project's data hook can allow any address to mint its tokens.
200
- /// @param projectId The ID of the project whose token can be minted.
201
- /// @param addr The address to check the token minting permission of.
202
- /// @return flag A flag indicating whether the address has permission to mint the project's tokens on-demand.
203
- function hasMintPermissionFor(uint256 projectId, JBRuleset memory, address addr) external view returns (bool flag) {
204
- // If the address is a sucker for this project.
205
- return SUCKER_REGISTRY.isSuckerOf({projectId: projectId, addr: addr});
206
- }
207
-
208
- /// @dev Make sure only mints can be received.
209
- function onERC721Received(
210
- address operator,
211
- address from,
212
- uint256 tokenId,
213
- bytes calldata data
214
- )
215
- external
216
- view
217
- returns (bytes4)
218
- {
219
- data;
220
- tokenId;
221
- operator;
222
-
223
- // Make sure the 721 received is the JBProjects contract.
224
- if (msg.sender != address(PROJECTS)) revert();
225
- // Make sure the 721 is being received as a mint.
226
- if (from != address(0)) revert();
227
- return IERC721Receiver.onERC721Received.selector;
228
- }
229
-
230
- //*********************************************************************//
231
- // -------------------------- public views --------------------------- //
232
- //*********************************************************************//
233
-
234
- /// @notice Indicates if this contract adheres to the specified interface.
235
- /// @dev See `IERC165.supportsInterface`.
236
- /// @return A flag indicating if the provided interface ID is supported.
237
- function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
238
- return interfaceId == type(ICTDeployer).interfaceId || interfaceId == type(IJBRulesetDataHook).interfaceId
239
- || interfaceId == type(IERC721Receiver).interfaceId;
240
- }
241
-
242
120
  //*********************************************************************//
243
121
  // ---------------------- external transactions ---------------------- //
244
122
  //*********************************************************************//
@@ -408,6 +286,118 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
408
286
  });
409
287
  }
410
288
 
289
+ //*********************************************************************//
290
+ // ------------------------- external views -------------------------- //
291
+ //*********************************************************************//
292
+
293
+ /// @notice Allow cash outs from suckers without a tax.
294
+ /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a cash out.
295
+ /// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
296
+ /// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
297
+ /// out.
298
+ /// @return cashOutCount The number of project tokens that are cashed out.
299
+ /// @return totalSupply The total project token supply.
300
+ /// @return surplusValue The surplus value to use for the bonding curve calculation.
301
+ /// @return hookSpecifications The amount of funds and the data to send to cash out hooks (this contract).
302
+ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
303
+ external
304
+ view
305
+ override
306
+ returns (
307
+ uint256 cashOutTaxRate,
308
+ uint256 cashOutCount,
309
+ uint256 totalSupply,
310
+ uint256 surplusValue,
311
+ JBCashOutHookSpecification[] memory hookSpecifications
312
+ )
313
+ {
314
+ // If the cash out is from a sucker, return the full cash out amount without taxes or fees.
315
+ if (SUCKER_REGISTRY.isSuckerOf({projectId: context.projectId, addr: context.holder})) {
316
+ return (0, context.cashOutCount, context.totalSupply, context.surplus.value, hookSpecifications);
317
+ }
318
+
319
+ // If the ruleset has a data hook, forward the call to the datahook.
320
+ IJBRulesetDataHook hook = dataHookOf[context.projectId];
321
+ if (address(hook) == address(0)) {
322
+ return (
323
+ context.cashOutTaxRate,
324
+ context.cashOutCount,
325
+ context.totalSupply,
326
+ context.surplus.value,
327
+ hookSpecifications
328
+ );
329
+ }
330
+ // slither-disable-next-line unused-return
331
+ return hook.beforeCashOutRecordedWith(context);
332
+ }
333
+
334
+ /// @notice Forward the call to the original data hook.
335
+ /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a payment.
336
+ /// @param context Standard Juicebox payment context. See `JBBeforePayRecordedContext`.
337
+ /// @return weight The weight which project tokens are minted relative to. This can be used to customize how many
338
+ /// tokens get minted by a payment.
339
+ /// @return hookSpecifications Amounts (out of what's being paid in) to be sent to pay hooks instead of being paid
340
+ /// into the project. Useful for automatically routing funds from a treasury as payments come in.
341
+ function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
342
+ external
343
+ view
344
+ override
345
+ returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
346
+ {
347
+ // Forward the call to the data hook.
348
+ IJBRulesetDataHook hook = dataHookOf[context.projectId];
349
+ if (address(hook) == address(0)) {
350
+ return (context.weight, hookSpecifications);
351
+ }
352
+
353
+ // slither-disable-next-line unused-return
354
+ return hook.beforePayRecordedWith(context);
355
+ }
356
+
357
+ /// @notice A flag indicating whether an address has permission to mint a project's tokens on-demand.
358
+ /// @dev A project's data hook can allow any address to mint its tokens.
359
+ /// @param projectId The ID of the project whose token can be minted.
360
+ /// @param addr The address to check the token minting permission of.
361
+ /// @return flag A flag indicating whether the address has permission to mint the project's tokens on-demand.
362
+ function hasMintPermissionFor(uint256 projectId, JBRuleset memory, address addr) external view returns (bool flag) {
363
+ // If the address is a sucker for this project.
364
+ return SUCKER_REGISTRY.isSuckerOf({projectId: projectId, addr: addr});
365
+ }
366
+
367
+ /// @dev Make sure only mints can be received.
368
+ function onERC721Received(
369
+ address operator,
370
+ address from,
371
+ uint256 tokenId,
372
+ bytes calldata data
373
+ )
374
+ external
375
+ view
376
+ returns (bytes4)
377
+ {
378
+ data;
379
+ tokenId;
380
+ operator;
381
+
382
+ // Make sure the 721 received is the JBProjects contract.
383
+ if (msg.sender != address(PROJECTS)) revert();
384
+ // Make sure the 721 is being received as a mint.
385
+ if (from != address(0)) revert();
386
+ return IERC721Receiver.onERC721Received.selector;
387
+ }
388
+
389
+ //*********************************************************************//
390
+ // -------------------------- public views --------------------------- //
391
+ //*********************************************************************//
392
+
393
+ /// @notice Indicates if this contract adheres to the specified interface.
394
+ /// @dev See `IERC165.supportsInterface`.
395
+ /// @return A flag indicating if the provided interface ID is supported.
396
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
397
+ return interfaceId == type(ICTDeployer).interfaceId || interfaceId == type(IJBRulesetDataHook).interfaceId
398
+ || interfaceId == type(IERC721Receiver).interfaceId;
399
+ }
400
+
411
401
  //*********************************************************************//
412
402
  // --------------------- internal transactions ----------------------- //
413
403
  //*********************************************************************//
@@ -451,7 +441,7 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
451
441
  }
452
442
 
453
443
  //*********************************************************************//
454
- // ------------------------ internal functions ----------------------- //
444
+ // -------------------------- internal views ------------------------- //
455
445
  //*********************************************************************//
456
446
 
457
447
  /// @dev ERC-2771 specifies the context as being a single address (20 bytes).