@bananapus/omnichain-deployers-v6 0.0.19 → 0.0.21

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/SKILLS.md CHANGED
@@ -1,253 +1,40 @@
1
1
  # Juicebox Omnichain Deployers
2
2
 
3
- ## Purpose
4
-
5
- Single-transaction deployment of Juicebox projects with cross-chain suckers and 721 tiers hooks. Every project gets a 721 hook (even with 0 initial tiers), so projects can add NFT tiers later without reconfiguring. Wraps the project's data hooks to give suckers tax-free cash outs and mint permission without interfering with custom hooks. Stores the 721 hook per-ruleset in `_tiered721HookOf` and the optional custom hook per-ruleset in `_extraDataHookOf`, each with their own `useDataHookForCashOut` flag. Supports composing a 721 hook alongside a custom data hook (e.g., buyback hook) — both run on every payment.
6
-
7
- ## Contracts
8
-
9
- | Contract | Role |
10
- |----------|------|
11
- | `JBOmnichainDeployer` | Deploys projects/rulesets/suckers, wraps data hooks for sucker tax exemption. Always deploys a 721 hook. Stores 721 hook per-ruleset in `_tiered721HookOf` and custom hook per-ruleset in `_extraDataHookOf`. Implements `IJBRulesetDataHook`, `IERC721Receiver`, `ERC2771Context`, `JBPermissioned`. |
12
-
13
- ## Key Functions
14
-
15
- ### Deployment
16
-
17
- | Function | What it does |
18
- |----------|-------------|
19
- | `launchProjectFor(owner, projectUri, deploy721Config, rulesetConfigs, terminalConfigs, memo, suckerConfig, controller)` | Creates a new project with a 721 tiers hook, rulesets, terminals, and suckers in one tx. Always deploys a 721 hook (even with 0 tiers). Temporarily holds the project NFT. Returns `(projectId, hook, suckers)`. |
20
- | `launchProjectFor(owner, projectUri, rulesetConfigs, terminalConfigs, memo, suckerConfig, controller)` | Simplified overload — omits `deploy721Config`, uses `_default721Config` (empty tiers, baseCurrency from first ruleset, decimals=18). |
21
- | `launchRulesetsFor(projectId, deploy721Config, rulesetConfigs, terminalConfigs, memo, controller)` | Launches new rulesets + terminals with a new 721 tiers hook for an existing project. Requires `LAUNCH_RULESETS` + `SET_TERMINALS`. Returns `(rulesetId, hook)`. |
22
- | `launchRulesetsFor(projectId, rulesetConfigs, terminalConfigs, memo, controller)` | Simplified overload — omits `deploy721Config`, uses `_default721Config`. |
23
- | `queueRulesetsOf(projectId, deploy721Config, rulesetConfigs, memo, controller)` | Queues future rulesets. If tiers provided, deploys a new 721 hook. Otherwise, carries forward the 721 hook from the latest ruleset. Requires `QUEUE_RULESETS`. Reverts if rulesets were already queued in the same block. Returns `(rulesetId, hook)`. |
24
- | `queueRulesetsOf(projectId, rulesetConfigs, memo, controller)` | Simplified overload — omits `deploy721Config`, uses `_default721Config`. With 0 tiers, always carries forward the existing hook. |
25
- | `deploySuckersFor(projectId, suckerConfig)` | Deploys new suckers for an existing project. Requires `DEPLOY_SUCKERS`. |
26
-
27
- ### Data Hook (IJBRulesetDataHook)
28
-
29
- | Function | What it does |
30
- |----------|-------------|
31
- | `beforePayRecordedWith(context)` | Calls the 721 hook first (via `_tiered721HookOf`) for its specs (including split amounts), then calls the custom hook from `_extraDataHookOf` (if `useDataHookForPay: true`) with a reduced amount context (payment minus split amount) for weight + specs. Adjusts the returned weight proportionally so the terminal only mints tokens for the amount entering the project (`weight = mulDiv(weight, amount - splitAmount, amount)`). Merges specs (721 hook specs first if any, then custom hook specs). If the 721 hook returns no specs (0 tiers), its slot is omitted from the output. |
32
- | `beforeCashOutRecordedWith(context)` | If holder is a sucker: returns 0% tax immediately. Calls the 721 hook first (if `useDataHookForCashOut: true`), updating cash out parameters. Then calls the custom hook from `_extraDataHookOf` (if `useDataHookForCashOut: true`) with the already-updated values. Both hooks' specifications are merged (721 specs first, then custom hook specs). If the 721 hook has the flag set and reverts (e.g., fungible cashout), the revert propagates. If neither has the flag set, returns original values. Hook specifications include a `noop` field — the 721 hook always returns `noop: false` (it needs its callback), while a custom hook like the buyback hook may return `noop: true` with routing diagnostics when the protocol path wins. |
33
- | `hasMintPermissionFor(projectId, ruleset, addr)` | Returns `true` for registered suckers, OR if the custom hook in `_extraDataHookOf` grants permission. Returns `false` only if it doesn't grant it. |
34
-
35
- ### Views
36
-
37
- | Function | What it does |
38
- |----------|-------------|
39
- | `extraDataHookOf(projectId, rulesetId)` | Returns the stored `JBDeployerHookConfig` for a given project and ruleset. Contains the custom data hook (e.g., buyback hook) with its per-hook flags. Returns empty struct if none configured. |
40
- | `tiered721HookOf(projectId, rulesetId)` | Returns the 721 tiers hook and its `useDataHookForCashOut` flag for a given project and ruleset. |
41
- | `supportsInterface(interfaceId)` | Returns `true` for `IJBOmnichainDeployer`, `IJBRulesetDataHook`, `IERC721Receiver`, `IERC165`. |
42
- | `onERC721Received(...)` | Accepts project NFTs from `PROJECTS` only. Reverts for any other NFT contract. |
43
-
44
- ## Integration Points
45
-
46
- | Dependency | Import | Used For |
47
- |------------|--------|----------|
48
- | `nana-core-v6` | `IJBController`, `JBPermissioned`, `IJBProjects`, `IJBRulesetDataHook` | Launching projects, permission checks, project NFT transfers, data hook interface |
49
- | `nana-721-hook-v6` | `IJB721TiersHookDeployer`, `JBDeploy721TiersHookConfig` | Deploying 721 tiers hooks |
50
- | `nana-suckers-v6` | `IJBSuckerRegistry` | Deploying suckers, checking `isSuckerOf()` for tax-free cash outs |
51
- | `nana-ownable-v6` | `JBOwnable` | Transferring 721 hook ownership to the project |
52
- | `nana-permission-ids-v6` | `JBPermissionIds` | Permission constants |
53
- | `@openzeppelin/contracts` | `ERC2771Context`, `IERC721Receiver` | Meta-transaction support, receiving project NFTs |
54
- | `@prb/math` | `mulDiv` | Weight scaling for 721 tier split amounts in `beforePayRecordedWith` |
55
-
56
- ## Key Types
57
-
58
- | Struct | Key Fields | Used In |
59
- |--------|------------|---------|
60
- | `JBOmnichain721Config` | `JBDeploy721TiersHookConfig deployTiersHookConfig`, `bool useDataHookForCashOut`, `bytes32 salt` | All deploy/launch/queue functions — 721 hook deployment config. |
61
- | `JBDeployerHookConfig` | `IJBRulesetDataHook dataHook`, `bool useDataHookForPay`, `bool useDataHookForCashOut` | `_extraDataHookOf` mapping keyed by `(projectId, rulesetId)` → single custom hook config. |
62
- | `JBTiered721HookConfig` | `IJB721TiersHook hook`, `bool useDataHookForCashOut` | `_tiered721HookOf` mapping keyed by `(projectId, rulesetId)` → per-ruleset 721 hook config. |
63
- | `JBSuckerDeploymentConfig` | `JBSuckerDeployerConfig[] deployerConfigurations`, `bytes32 salt` | All launch and deploy functions |
64
-
65
- ## Permission IDs
66
-
67
- | Permission | Used By |
68
- |------------|---------|
69
- | `DEPLOY_SUCKERS` | `deploySuckersFor` -- deploy new suckers for a project |
70
- | `LAUNCH_RULESETS` | `launchRulesetsFor` -- launch rulesets with terminal configuration |
71
- | `QUEUE_RULESETS` | `queueRulesetsOf` -- queue future rulesets |
72
- | `SET_TERMINALS` | `launchRulesetsFor` -- set terminal configurations |
73
- | `MAP_SUCKER_TOKEN` | Granted to `SUCKER_REGISTRY` at construction with `projectId=0` (all projects) |
74
-
75
- ## Errors
76
-
77
- | Error | When |
78
- |-------|------|
79
- | `JBOmnichainDeployer_InvalidHook` | `_setup721()` detects the hook is `address(this)` (prevents infinite forwarding loops), OR `queueRulesetsOf` tries to carry forward a null hook (no tiers provided and no previous hook deployed through this contract) |
80
- | `JBOmnichainDeployer_UnexpectedNFTReceived` | `onERC721Received` called by a contract other than `PROJECTS` |
81
- | `JBOmnichainDeployer_RulesetIdsUnpredictable` | `queueRulesetsOf` called when `latestRulesetIdOf(projectId) >= block.timestamp` -- ruleset ID prediction would fail |
82
- | `JBOmnichainDeployer_ProjectIdMismatch` | `launchProjectFor` -- the project ID returned by the controller does not match the predicted `PROJECTS.count() + 1` |
83
- | `JBOmnichainDeployer_ControllerMismatch` | `launchRulesetsFor`/`queueRulesetsOf` -- the provided controller does not match the project's controller in `JBDirectory` |
84
-
85
- ## Events
86
-
87
- `JBOmnichainDeployer` does not declare any custom events. All observable state changes (project creation, ruleset queuing, sucker deployment) are emitted by the underlying contracts it calls (`IJBController`, `IJBSuckerRegistry`, `IJB721TiersHookDeployer`).
3
+ ## Use This File For
88
4
 
89
- ## Constants
5
+ - Use this file when the task involves deploying core projects with suckers and optional 721 or custom data-hook composition in one flow.
6
+ - Start here, then open the deployer or composition-focused tests depending on whether the question is about setup, permissions, hook wrapping, or runtime delegation.
90
7
 
91
- | Name | Value | Context |
92
- |------|-------|---------|
93
- | `PROJECTS` | Set at construction | `IJBProjects` -- mints project NFTs. Immutable. |
94
- | `HOOK_DEPLOYER` | Set at construction | `IJB721TiersHookDeployer` -- deploys 721 tiers hooks. Immutable. |
95
- | `SUCKER_REGISTRY` | Set at construction | `IJBSuckerRegistry` -- deploys/tracks suckers, `isSuckerOf` checks. Immutable. |
96
- | `projectId = 0` (wildcard) | Used in constructor | `MAP_SUCKER_TOKEN` permission granted to `SUCKER_REGISTRY` with `projectId=0`, giving it token mapping rights for all projects. |
97
- | `decimals = 18` | Used in `_default721Config` | Default decimal precision when 721 config is omitted (simplified overloads). |
98
- | `baseCurrency` | From first ruleset | When 721 config is omitted, `baseCurrency` is read from `rulesetConfigurations[0].metadata.baseCurrency`. Reverts with `JBOmnichainDeployer_NoRulesetConfigurations` if the array is empty. |
8
+ ## Read This Next
99
9
 
100
- ## Gotchas
10
+ | If you need... | Open this next |
11
+ |---|---|
12
+ | Repo overview and launch model | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
13
+ | Deployment implementation | [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol) |
14
+ | Input and output types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
15
+ | Guard rails, attacks, or hook composition behavior | [`test/JBOmnichainDeployerGuard.t.sol`](./test/JBOmnichainDeployerGuard.t.sol), [`test/OmnichainDeployerAttacks.t.sol`](./test/OmnichainDeployerAttacks.t.sol), [`test/Tiered721HookComposition.t.sol`](./test/Tiered721HookComposition.t.sol), [`test/regression/`](./test/regression/) |
101
16
 
102
- ### Deployment
17
+ ## Repo Map
103
18
 
104
- - `launchProjectFor` requires **no permissions** -- anyone can launch a project to any owner address.
105
- - `queueRulesetsOf` **reverts if called in the same block** as a previous ruleset queue (whether via deployer or directly). The `launchProjectFor` function doesn't have this guard because it predicts IDs from `PROJECTS.count()`, which is always 0 for a new project.
106
- - Ruleset IDs in `_extraDataHookOf` are keyed by `block.timestamp + i`. If the controller assigns different IDs than predicted, the stored hook configs will be orphaned and the deployer will behave as if no hooks were set (returning default values).
107
- - Sucker deployment salts are hashed with `_msgSender()`: `keccak256(abi.encode(salt, _msgSender()))`. Cross-chain deterministic addresses require using the **same sender** on each chain. The 721 hook salt uses `keccak256(abi.encode(_msgSender(), salt))` (reversed order).
108
- - `salt = bytes32(0)` **skips sucker deployment entirely**. Use a nonzero salt to deploy suckers.
109
- - Hook ownership is transferred to the **project** (not the owner) via `JBOwnable.transferOwnershipToProject(projectId)`. This happens **after** the project NFT is minted.
110
- - The deployer holds the project NFT temporarily during launch. If the controller's `launchProjectFor` reverts, the entire transaction reverts -- no stuck NFTs.
111
- - Every project always gets a 721 hook, even with an empty tiers array. This wires up the 721 hook from the start, so tiers can be added later without reconfiguring the data hook.
112
- - For `queueRulesetsOf`, if no new tiers are provided, the 721 hook from the **latest ruleset** is carried forward instead of deploying a new one. Looked up from `_tiered721HookOf[projectId][latestRulesetId]`.
19
+ | Area | Where to look |
20
+ |---|---|
21
+ | Main contract | [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol) |
22
+ | Types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
23
+ | Scripts | [`script/`](./script/) |
24
+ | Tests | [`test/`](./test/) |
113
25
 
114
- ### Data Hook Behavior
115
-
116
- - The deployer **always forces `useDataHookForCashOut = true`** at the protocol level so it can intercept cash outs for sucker tax exemption. However, the 721 hook's `useDataHookForCashOut` flag (stored in `_tiered721HookOf`) and the custom hook's flag (stored in `_extraDataHookOf`) each control whether that hook processes cash outs. Set `useDataHookForCashOut: false` on the 721 config to skip it for fungible cashouts (it reverts with `JB721Hook_UnexpectedTokenCashedOut` otherwise).
117
- - Suckers get an **early return** in `beforeCashOutRecordedWith` -- they bypass all stored hooks entirely. Suckers can cash out even if any hook would revert.
118
- - If no custom hook is stored or it doesn't grant permission, `hasMintPermissionFor` returns `false` for non-suckers. Only the custom hook in `_extraDataHookOf` is checked -- the 721 hook is not consulted.
119
- - `_setup721()` sets `metadata.dataHook = address(this)`, `metadata.useDataHookForPay = true`, and `metadata.useDataHookForCashOut = true` on every ruleset. These cannot be overridden.
120
- - All data hook functions (`beforePayRecordedWith`, `beforeCashOutRecordedWith`, `hasMintPermissionFor`) are `view`. If the project's real hook needs to modify state in these functions, it will fail.
121
- - Setting a hook's `dataHook` to `address(this)` (the deployer itself) reverts with `JBOmnichainDeployer_InvalidHook`. This prevents infinite forwarding loops.
122
- - The 721 hook is stored per-ruleset in `_tiered721HookOf[projectId][rulesetId]` with its `useDataHookForCashOut` flag. The custom data hook (if any) is stored separately in `_extraDataHookOf[projectId][rulesetId]`. They are never in the same mapping.
123
- - The `JBOmnichain721Config` parameter bundles the 721 hook deployment config, the `useDataHookForCashOut` flag, and the `salt`. Custom data hooks are read from each ruleset's `metadata.dataHook` field.
124
-
125
- ### Permissions
126
-
127
- - The constructor grants `MAP_SUCKER_TOKEN` permission to `SUCKER_REGISTRY` with `projectId=0`, meaning the registry can map tokens for **any project** deployed through this deployer.
128
-
129
- ### Edge Cases
130
-
131
- - `onERC721Received` only accepts NFTs from the `PROJECTS` contract. Sending any other ERC-721 to the deployer will revert.
132
- - ERC2771 meta-transaction support allows gasless deployments via a trusted forwarder. Salt hashing uses `_msgSender()` (not `msg.sender`), so forwarder-relayed transactions use the original sender's address for deterministic sucker addresses.
133
-
134
- ## Example Integration
135
-
136
- ```solidity
137
- import {IJBOmnichainDeployer} from "@bananapus/omnichain-deployers-v6/src/interfaces/IJBOmnichainDeployer.sol";
138
- import {JBOmnichain721Config} from "@bananapus/omnichain-deployers-v6/src/structs/JBOmnichain721Config.sol";
139
- import {JBSuckerDeploymentConfig} from "@bananapus/omnichain-deployers-v6/src/structs/JBSuckerDeploymentConfig.sol";
140
- import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
141
- import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
142
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
143
- import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
144
-
145
- // --- Launch a project with suckers ---
146
-
147
- // Configure sucker deployment (use a nonzero salt to deploy suckers).
148
- JBSuckerDeploymentConfig memory suckerConfig = JBSuckerDeploymentConfig({
149
- deployerConfigurations: suckerDeployerConfigs, // per-chain sucker configs
150
- salt: bytes32("my-project-salt") // deterministic addresses
151
- });
152
-
153
- // Configure 721 hook (even with 0 tiers, every project gets a hook).
154
- JBOmnichain721Config memory deploy721Config = JBOmnichain721Config({
155
- deployTiersHookConfig: tiersHookConfig, // tier configuration (can be empty)
156
- useDataHookForCashOut: false, // set true for NFT-based cashouts
157
- salt: bytes32("my-hook-salt") // deterministic 721 hook address
158
- });
159
-
160
- // Launch in one transaction.
161
- (uint256 projectId, IJB721TiersHook hook, address[] memory suckers) = omnichainDeployer.launchProjectFor({
162
- owner: msg.sender,
163
- projectUri: "ipfs://project-metadata",
164
- deploy721Config: deploy721Config,
165
- rulesetConfigurations: rulesetConfigs,
166
- terminalConfigurations: terminalConfigs,
167
- memo: "Launching omnichain project",
168
- suckerDeploymentConfiguration: suckerConfig,
169
- controller: controller
170
- });
171
-
172
- // --- Add suckers to an existing project ---
173
-
174
- // Requires DEPLOY_SUCKERS permission on the project.
175
- address[] memory newSuckers = omnichainDeployer.deploySuckersFor({
176
- projectId: projectId,
177
- suckerDeploymentConfiguration: suckerConfig
178
- });
179
-
180
- // --- Queue new rulesets (carries forward existing 721 hook if no new tiers) ---
181
-
182
- // Requires QUEUE_RULESETS permission. Must be called in a different block
183
- // than any previous ruleset queue for this project.
184
- // Custom data hooks are read from each ruleset's metadata.dataHook field.
185
- JBOmnichain721Config memory queue721Config = JBOmnichain721Config({
186
- deployTiersHookConfig: emptyTiersConfig, // no new tiers = carry forward existing hook
187
- useDataHookForCashOut: false,
188
- salt: bytes32(0) // no salt needed when carrying forward
189
- });
190
-
191
- (uint256 rulesetId, IJB721TiersHook queuedHook) = omnichainDeployer.queueRulesetsOf({
192
- projectId: projectId,
193
- deploy721Config: queue721Config,
194
- rulesetConfigurations: queueConfig,
195
- memo: "Queue new rulesets",
196
- controller: controller
197
- });
198
- ```
199
-
200
- ## Buyback Hook + 721 Hook Composition
201
-
202
- The most complex use case: a project with NFT tiers (721 hook) AND a buyback hook, both running on every payment. The deployer composes them automatically.
203
-
204
- ```solidity
205
- import {IJBBuybackHook} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHook.sol";
206
- import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
26
+ ## Purpose
207
27
 
208
- // --- Key concept: the buyback hook goes in metadata.dataHook ---
209
- // The deployer extracts it, stores it in _extraDataHookOf, and replaces
210
- // metadata.dataHook with address(this) so it can intercept all calls.
28
+ Single-transaction deployment and wrapper layer for Juicebox projects that need a 721 hook, cross-chain sucker support, and optional extra data-hook composition from day one.
211
29
 
212
- // 1. Configure the buyback hook as the ruleset's custom data hook.
213
- JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
214
- rulesetConfigs[0].metadata = JBRulesetMetadata({
215
- // ... other metadata fields ...
216
- dataHook: address(buybackHook), // <-- deployer extracts this
217
- useDataHookForPay: true, // buyback hook processes payments
218
- useDataHookForCashOut: false // buyback hook does NOT process cashouts
219
- // ...
220
- });
30
+ ## Reference Files
221
31
 
222
- // 2. Configure the 721 hook with NFT tiers.
223
- JBOmnichain721Config memory deploy721Config = JBOmnichain721Config({
224
- deployTiersHookConfig: tiersHookConfig, // your NFT tier config
225
- useDataHookForCashOut: false, // false = skip 721 on fungible cashouts
226
- salt: bytes32("my-hook-salt")
227
- });
32
+ - Open [`references/runtime.md`](./references/runtime.md) when you need hook-composition order, sucker exemptions, per-ruleset storage behavior, or the main runtime invariants.
33
+ - Open [`references/operations.md`](./references/operations.md) when you need launch and queue behavior, permission and salt assumptions, test breadcrumbs, or the common stale-data traps.
228
34
 
229
- // 3. Launch -- both hooks are wired up automatically.
230
- (uint256 projectId, IJB721TiersHook hook, address[] memory suckers) =
231
- omnichainDeployer.launchProjectFor({
232
- owner: msg.sender,
233
- projectUri: "ipfs://metadata",
234
- deploy721Config: deploy721Config,
235
- rulesetConfigurations: rulesetConfigs,
236
- terminalConfigurations: terminalConfigs,
237
- memo: "Buyback + 721 project",
238
- suckerDeploymentConfiguration: suckerConfig,
239
- controller: controller
240
- });
35
+ ## Working Rules
241
36
 
242
- // --- What happens on each payment: ---
243
- // 1. beforePayRecordedWith calls the 721 hook first (tier matching, NFT minting specs)
244
- // 2. Reduces the payment amount by the 721 split amount
245
- // 3. Calls the buyback hook with the reduced amount (so it only buys back with leftover)
246
- // 4. Adjusts weight proportionally: weight = mulDiv(weight, amount - splitAmount, amount)
247
- // 5. Merges specs: 721 specs first, then buyback specs
248
- //
249
- // --- What happens on cashout: ---
250
- // With useDataHookForCashOut: false on both hooks:
251
- // - Suckers still get 0% tax (the deployer intercepts before any hook)
252
- // - Regular users get standard bonding curve cashout (no hook interference)
253
- ```
37
+ - Start in [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol) for both deployment and runtime wrapping behavior. This repo is orchestration plus a live data-hook wrapper, not a pure deploy script package.
38
+ - Treat ruleset ID prediction, hook-carry-forward logic, and salt derivation as high-risk. Small changes there can orphan config or break deterministic deployment.
39
+ - When a bug mentions suckers, tax-free cash outs, or mint permission, verify whether the early-return wrapper behavior is the real cause before changing downstream hooks.
40
+ - If a task mentions 721 or custom-hook behavior, confirm whether the issue lives here or in the composed repo before patching wrapper logic.
package/STYLE_GUIDE.md CHANGED
@@ -26,8 +26,8 @@ pragma solidity 0.8.28;
26
26
  // Interfaces, structs, enums — caret for forward compatibility
27
27
  pragma solidity ^0.8.0;
28
28
 
29
- // Libraries — caret, may use newer features
30
- pragma solidity ^0.8.17;
29
+ // Libraries — pin to exact version like contracts
30
+ pragma solidity 0.8.28;
31
31
  ```
32
32
 
33
33
  ## Imports
@@ -86,12 +86,20 @@ contract JBExample is JBPermissioned, IJBExample {
86
86
 
87
87
  uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
88
88
 
89
+ //*********************************************************************//
90
+ // ------------------------ private constants ------------------------ //
91
+ //*********************************************************************//
92
+
89
93
  //*********************************************************************//
90
94
  // --------------- public immutable stored properties ---------------- //
91
95
  //*********************************************************************//
92
96
 
93
97
  IJBDirectory public immutable override DIRECTORY;
94
98
 
99
+ //*********************************************************************//
100
+ // -------------- internal immutable stored properties -------------- //
101
+ //*********************************************************************//
102
+
95
103
  //*********************************************************************//
96
104
  // --------------------- public stored properties -------------------- //
97
105
  //*********************************************************************//
@@ -100,10 +108,26 @@ contract JBExample is JBPermissioned, IJBExample {
100
108
  // -------------------- internal stored properties ------------------- //
101
109
  //*********************************************************************//
102
110
 
111
+ //*********************************************************************//
112
+ // -------------------- private stored properties -------------------- //
113
+ //*********************************************************************//
114
+
115
+ //*********************************************************************//
116
+ // ------------------- transient stored properties ------------------- //
117
+ //*********************************************************************//
118
+
103
119
  //*********************************************************************//
104
120
  // -------------------------- constructor ---------------------------- //
105
121
  //*********************************************************************//
106
122
 
123
+ //*********************************************************************//
124
+ // ---------------------------- modifiers ---------------------------- //
125
+ //*********************************************************************//
126
+
127
+ //*********************************************************************//
128
+ // ------------------------- receive / fallback ---------------------- //
129
+ //*********************************************************************//
130
+
107
131
  //*********************************************************************//
108
132
  // ---------------------- external transactions ---------------------- //
109
133
  //*********************************************************************//
@@ -112,10 +136,18 @@ contract JBExample is JBPermissioned, IJBExample {
112
136
  // ----------------------- external views ---------------------------- //
113
137
  //*********************************************************************//
114
138
 
139
+ //*********************************************************************//
140
+ // -------------------------- public views --------------------------- //
141
+ //*********************************************************************//
142
+
115
143
  //*********************************************************************//
116
144
  // ----------------------- public transactions ----------------------- //
117
145
  //*********************************************************************//
118
146
 
147
+ //*********************************************************************//
148
+ // ---------------------- internal transactions ---------------------- //
149
+ //*********************************************************************//
150
+
119
151
  //*********************************************************************//
120
152
  // ----------------------- internal helpers -------------------------- //
121
153
  //*********************************************************************//
@@ -134,17 +166,28 @@ contract JBExample is JBPermissioned, IJBExample {
134
166
  1. Custom errors
135
167
  2. Public constants
136
168
  3. Internal constants
137
- 4. Public immutable stored properties
138
- 5. Internal immutable stored properties
139
- 6. Public stored properties
140
- 7. Internal stored properties
141
- 8. Constructor
142
- 9. External transactions
143
- 10. External views
144
- 11. Public transactions
145
- 12. Internal helpers
146
- 13. Internal views
147
- 14. Private helpers
169
+ 4. Private constants
170
+ 5. Public immutable stored properties
171
+ 6. Internal immutable stored properties
172
+ 7. Public stored properties
173
+ 8. Internal stored properties
174
+ 9. Private stored properties
175
+ 10. Transient stored properties
176
+ 11. Constructor
177
+ 12. Modifiers
178
+ 13. Receive / fallback
179
+ 14. External transactions
180
+ 15. External views
181
+ 16. Public views
182
+ 17. Public transactions
183
+ 18. Internal transactions
184
+ 19. Internal helpers
185
+ 20. Internal views
186
+ 21. Private helpers
187
+
188
+ Use these additional section labels where they better match the contents of the block:
189
+ - `internal functions` is accepted as equivalent to `internal helpers`
190
+ - `events` and `structs` are acceptable in specialized contracts that define them explicitly
148
191
 
149
192
  Functions are alphabetized within each section.
150
193
 
@@ -197,7 +240,7 @@ interface IJBExample is IJBBase {
197
240
  | Public/external function | `camelCase` | `cashOutTokensOf` |
198
241
  | Internal/private function | `_camelCase` | `_processFee` |
199
242
  | Internal storage | `_camelCase` | `_accountingContextForTokenOf` |
200
- | Function parameter | `camelCase` | `projectId`, `cashOutCount` |
243
+ | Function parameter | `camelCase` (no underscores) | `projectId`, `cashOutCount` |
201
244
 
202
245
  ## NatSpec
203
246
 
@@ -253,9 +296,12 @@ uint256 public constant MAX_RESERVED_PERCENT = 10_000;
253
296
 
254
297
  ## Function Calls
255
298
 
256
- Use named parameters for readability when calling functions with 3+ arguments:
299
+ Use named arguments for all function calls with 2 or more arguments — in both `src/` and `script/`:
257
300
 
258
301
  ```solidity
302
+ // Good — named arguments
303
+ token.mint({account: beneficiary, amount: count});
304
+ _transferOwnership({newOwner: address(0), projectId: 0});
259
305
  PERMISSIONS.hasPermission({
260
306
  operator: sender,
261
307
  account: account,
@@ -264,8 +310,18 @@ PERMISSIONS.hasPermission({
264
310
  includeRoot: true,
265
311
  includeWildcardProjectId: true
266
312
  });
313
+
314
+ // Bad — positional arguments with 2+ args
315
+ token.mint(beneficiary, count);
316
+ _transferOwnership(address(0), 0);
267
317
  ```
268
318
 
319
+ Single-argument calls use positional style: `_burn(amount)`.
320
+
321
+ This also applies to constructor calls, struct literals, and inherited/library calls (e.g., OZ `_mint`, `_safeMint`, `safeTransfer`, `allowance`, `Clones.cloneDeterministic`).
322
+
323
+ Named argument keys must use **camelCase** — never underscores. If a function's parameter names use underscores, rename them to camelCase first.
324
+
269
325
  ## Multiline Signatures
270
326
 
271
327
  ```solidity
@@ -552,7 +608,3 @@ CI checks formatting via `forge fmt --check`.
552
608
  ### Contract Size Checks
553
609
 
554
610
  CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
555
-
556
- ## Repo-Specific Deviations
557
-
558
- None. This repo follows the standard configuration exactly.