@bananapus/ownable-v6 0.0.6 → 0.0.7

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.
@@ -0,0 +1,75 @@
1
+ # Administration
2
+
3
+ Admin privileges and their scope in nana-ownable-v6.
4
+
5
+ ## Roles
6
+
7
+ | Role | Who | How Access Is Determined |
8
+ |------|-----|------------------------|
9
+ | **Direct Owner** | An EOA or contract stored in `JBOwner.owner` | Used when `JBOwner.projectId == 0`. Set at construction or via `transferOwnership(address)`. |
10
+ | **Project Owner** | The holder of the `JBProjects` ERC-721 for `JBOwner.projectId` | Used when `JBOwner.projectId != 0`. Resolved dynamically via `PROJECTS.ownerOf(projectId)` on every call. |
11
+ | **Permission Delegate** | Any address granted `JBOwner.permissionId` through `JBPermissions` | The owner (direct or project) calls `JBPermissions.setPermissionsFor(...)` to grant `permissionId` to an operator. That operator then passes the `_checkOwner()` / `_requirePermissionFrom()` check. |
12
+
13
+ Only one of Direct Owner or Project Owner is active at a time, never both. Permission Delegates extend whichever mode is active.
14
+
15
+ ## Privileged Functions
16
+
17
+ ### JBOwnableOverrides (abstract base)
18
+
19
+ | Function | Required Role | Permission ID | Scope | What It Does |
20
+ |----------|--------------|---------------|-------|--------------|
21
+ | `renounceOwnership()` | Owner or delegate | `jbOwner.permissionId` | Per-contract | Sets `owner` to `address(0)` and `projectId` to `0`. Permanently disables all `onlyOwner`-guarded functions. Irreversible. |
22
+ | `setPermissionId(uint8)` | Owner or delegate | `jbOwner.permissionId` | Per-contract | Changes which permission ID grants owner-equivalent access via `JBPermissions`. Resets the delegation surface -- previous delegates with the old ID lose access. |
23
+ | `transferOwnership(address)` | Owner or delegate | `jbOwner.permissionId` | Per-contract | Transfers ownership to a new address. Resets `projectId` to `0` and `permissionId` to `0`. The new owner must call `setPermissionId()` to re-enable delegation. |
24
+ | `transferOwnershipToProject(uint256)` | Owner or delegate | `jbOwner.permissionId` | Per-contract | Transfers ownership to a Juicebox project. Resets `owner` to `address(0)` and `permissionId` to `0`. Validates that the project exists (`projectId <= PROJECTS.count()`). |
25
+
26
+ ### JBOwnable (concrete contract)
27
+
28
+ `JBOwnable` inherits all functions above and adds no additional privileged functions. It provides the `onlyOwner` modifier for use by inheriting contracts.
29
+
30
+ Any contract that inherits `JBOwnable` and applies the `onlyOwner` modifier to its own functions effectively creates additional privileged functions gated by the same ownership and permission model described here.
31
+
32
+ ## Ownership Model
33
+
34
+ JBOwnable bridges Juicebox project ownership to the OpenZeppelin `Ownable` pattern through the `JBOwner` struct:
35
+
36
+ ```
37
+ JBOwner {
38
+ address owner; // Direct owner address (when projectId == 0)
39
+ uint88 projectId; // JB project whose NFT holder is owner (when != 0)
40
+ uint8 permissionId; // Permission ID for delegation via JBPermissions
41
+ }
42
+ ```
43
+
44
+ **Resolution logic** (in `_checkOwner()` and `owner()`):
45
+
46
+ 1. If `projectId != 0`, the owner is `PROJECTS.ownerOf(projectId)` -- resolved dynamically on every call. If `ownerOf()` reverts (e.g., hypothetical NFT burn), the resolved owner becomes `address(0)`, effectively renouncing the contract.
47
+ 2. If `projectId == 0`, the owner is `JBOwner.owner` directly.
48
+ 3. In both cases, `_checkOwner()` calls `_requirePermissionFrom(resolvedOwner, projectId, permissionId)`, which passes if `msg.sender` is the resolved owner OR has the configured `permissionId` granted through `JBPermissions`.
49
+
50
+ **Permission delegation** uses the nana-core `JBPermissions` contract. The owner calls `JBPermissions.setPermissionsFor(...)` to grant `permissionId` to an operator address. That operator can then call any `onlyOwner` function on this contract. The ROOT permission (ID 255) in `JBPermissions` grants all permission IDs, including whatever `permissionId` is configured here.
51
+
52
+ **Ownership transfer resets `permissionId` to 0.** This prevents the previous owner's delegates from retaining access after a transfer. The new owner must explicitly call `setPermissionId()` to configure delegation.
53
+
54
+ ## Immutable Configuration
55
+
56
+ | Property | Set At | Can Change? |
57
+ |----------|--------|-------------|
58
+ | `PERMISSIONS` (IJBPermissions) | Construction (via `JBPermissioned`) | No -- immutable |
59
+ | `PROJECTS` (IJBProjects) | Construction | No -- immutable |
60
+
61
+ The `JBPermissions` and `JBProjects` contract references are baked in at deploy time and cannot be changed. If either contract is upgraded or replaced, the `JBOwnable` instance must be redeployed.
62
+
63
+ ## Admin Boundaries
64
+
65
+ What admins **cannot** do:
66
+
67
+ - **Change the `PERMISSIONS` or `PROJECTS` contracts.** These are immutable references set at construction.
68
+ - **Set both `owner` and `projectId` simultaneously.** The `_transferOwnership` internal function reverts if both are non-zero.
69
+ - **Transfer to `address(0)` via `transferOwnership()`.** This reverts with `JBOwnableOverrides_InvalidNewOwner`. Use `renounceOwnership()` instead.
70
+ - **Transfer to a non-existent project.** `transferOwnershipToProject()` checks `projectId <= PROJECTS.count()` and reverts if the project does not exist.
71
+ - **Transfer to `projectId` 0 via `transferOwnershipToProject()`.** Reverts with `JBOwnableOverrides_InvalidNewOwner`.
72
+ - **Transfer to `projectId` exceeding `uint88`.** Reverts with `JBOwnableOverrides_InvalidNewOwner`.
73
+ - **Undo `renounceOwnership()`.** Once ownership is renounced, all `onlyOwner` functions are permanently disabled. There is no recovery mechanism.
74
+ - **Bypass `JBPermissions` for delegation.** Permission delegation is exclusively handled through the external `JBPermissions` contract; `JBOwnable` itself has no operator registry.
75
+ - **Prevent project NFT transfers from changing ownership.** When owned by a project, whoever holds the `JBProjects` ERC-721 is the owner. There is no veto or lock mechanism within `JBOwnable`.
@@ -0,0 +1,49 @@
1
+ # nana-ownable-v6 — Architecture
2
+
3
+ ## Purpose
4
+
5
+ Juicebox-aware ownership module. Extends OpenZeppelin's Ownable pattern to support ownership by either a Juicebox project (via ERC-721) or a direct address, with permission delegation through JBPermissions.
6
+
7
+ ## Contract Map
8
+
9
+ ```
10
+ src/
11
+ ├── JBOwnable.sol — Concrete ownable with constructor
12
+ ├── JBOwnableOverrides.sol — Abstract base with onlyOwner modifier logic
13
+ ├── interfaces/
14
+ │ └── IJBOwnable.sol — Interface for ownership queries and transfers
15
+ └── structs/
16
+ └── JBOwner.sol — Owner struct: {owner, projectId, permissionId}
17
+ ```
18
+
19
+ ## Ownership Model
20
+
21
+ ```
22
+ JBOwner {
23
+ address owner; — Direct owner address (if projectId == 0)
24
+ uint88 projectId; — JB project ID whose NFT holder is owner (if != 0)
25
+ uint8 permissionId; — Permission ID that grants owner access via JBPermissions
26
+ }
27
+
28
+ Resolution order:
29
+ 1. If projectId != 0 → owner = JBProjects.ownerOf(projectId)
30
+ 2. If projectId == 0 → owner = JBOwner.owner address
31
+ 3. Additional access via JBPermissions.hasPermission(operator, owner, projectId, permissionId)
32
+ ```
33
+
34
+ ## Key Operations
35
+
36
+ ### Ownership Transfer
37
+ ```
38
+ Current owner → transferOwnership(newOwner)
39
+ → Can transfer to address or project ID
40
+ → Emits OwnershipTransferred
41
+
42
+ Current owner → renounceOwnership()
43
+ → Sets owner to address(0), projectId to 0
44
+ → Permanently disables owner-only functions
45
+ ```
46
+
47
+ ## Dependencies
48
+ - `@bananapus/core-v6` — JBPermissioned, IJBProjects, IJBPermissions
49
+ - `@openzeppelin/contracts` — Context
package/RISKS.md ADDED
@@ -0,0 +1,23 @@
1
+ # nana-ownable-v6 — Risks
2
+
3
+ ## Trust Assumptions
4
+
5
+ 1. **JBPermissions** — Permission checks delegate to JBPermissions contract. A bug in JBPermissions affects all JBOwnable contracts.
6
+ 2. **JBProjects ERC-721** — When owned by a project, ownership follows the ERC-721 token. Whoever holds the project NFT has owner access.
7
+ 3. **Permission Delegation** — Anyone granted the configured `permissionId` via JBPermissions gets owner-equivalent access.
8
+
9
+ ## Known Risks
10
+
11
+ | Risk | Description | Mitigation |
12
+ |------|-------------|------------|
13
+ | Permission escalation | Granting `permissionId` gives full owner access to that function | Only grant to trusted operators |
14
+ | Project NFT transfer | Transferring the project NFT transfers ownership of all JBOwnable contracts tied to it | Intentional design; use multisig for project NFT |
15
+ | Renounce is permanent | `renounceOwnership()` is irreversible | Standard OpenZeppelin pattern |
16
+ | Zero address project | Setting `projectId = 0` with `owner = address(0)` permanently locks ownership | Validate before calling |
17
+
18
+ ## Privileged Roles
19
+
20
+ | Role | Access | Scope |
21
+ |------|--------|-------|
22
+ | Owner (address or project holder) | All `onlyOwner` functions | Per-contract |
23
+ | Permission delegates | `onlyOwner` functions via JBPermissions | Per-contract, per-permissionId |
package/STYLE_GUIDE.md ADDED
@@ -0,0 +1,468 @@
1
+ # Style Guide
2
+
3
+ How we write Solidity and organize repos across the Juicebox V6 ecosystem. `nana-core-v6` is the gold standard — when in doubt, match what it does.
4
+
5
+ ## File Organization
6
+
7
+ ```
8
+ src/
9
+ ├── Contract.sol # Main contracts in root
10
+ ├── abstract/ # Base contracts (JBPermissioned, JBControlled)
11
+ ├── enums/ # One enum per file
12
+ ├── interfaces/ # One interface per file, prefixed with I
13
+ ├── libraries/ # Pure/view logic, prefixed with JB
14
+ ├── periphery/ # Utility contracts (deadlines, price feeds)
15
+ └── structs/ # One struct per file, prefixed with JB
16
+ ```
17
+
18
+ One contract/interface/struct/enum per file. Name the file after the type it contains.
19
+
20
+ **Structs, enums, libraries, and interfaces always go in their subdirectories** (`src/structs/`, `src/enums/`, `src/libraries/`, `src/interfaces/`) — never inline in contract files or placed in `src/` root. This keeps type definitions discoverable and import paths consistent across repos.
21
+
22
+ ## Pragma Versions
23
+
24
+ ```solidity
25
+ // Contracts — pin to exact version
26
+ pragma solidity 0.8.26;
27
+
28
+ // Interfaces, structs, enums — caret for forward compatibility
29
+ pragma solidity ^0.8.0;
30
+
31
+ // Libraries — caret, may use newer features
32
+ pragma solidity ^0.8.17;
33
+ ```
34
+
35
+ ## Imports
36
+
37
+ Named imports only. Grouped by source, alphabetized within each group:
38
+
39
+ ```solidity
40
+ // External packages (alphabetized)
41
+ import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
42
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
43
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
44
+ import {mulDiv} from "@prb/math/src/Common.sol";
45
+
46
+ // Local: abstract contracts
47
+ import {JBPermissioned} from "./abstract/JBPermissioned.sol";
48
+
49
+ // Local: interfaces (alphabetized)
50
+ import {IJBController} from "./interfaces/IJBController.sol";
51
+ import {IJBDirectory} from "./interfaces/IJBDirectory.sol";
52
+ import {IJBMultiTerminal} from "./interfaces/IJBMultiTerminal.sol";
53
+
54
+ // Local: libraries (alphabetized)
55
+ import {JBConstants} from "./libraries/JBConstants.sol";
56
+ import {JBFees} from "./libraries/JBFees.sol";
57
+
58
+ // Local: structs (alphabetized)
59
+ import {JBAccountingContext} from "./structs/JBAccountingContext.sol";
60
+ import {JBSplit} from "./structs/JBSplit.sol";
61
+ ```
62
+
63
+ ## Contract Structure
64
+
65
+ Section banners divide the contract into a fixed ordering. Every contract with 50+ lines uses these banners:
66
+
67
+ ```solidity
68
+ /// @notice One-line description.
69
+ contract JBExample is JBPermissioned, IJBExample {
70
+ // A library that does X.
71
+ using SomeLib for SomeType;
72
+
73
+ //*********************************************************************//
74
+ // --------------------------- custom errors ------------------------- //
75
+ //*********************************************************************//
76
+
77
+ error JBExample_SomethingFailed(uint256 amount);
78
+
79
+ //*********************************************************************//
80
+ // ------------------------- public constants ------------------------ //
81
+ //*********************************************************************//
82
+
83
+ uint256 public constant override FEE = 25;
84
+
85
+ //*********************************************************************//
86
+ // ----------------------- internal constants ------------------------ //
87
+ //*********************************************************************//
88
+
89
+ uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
90
+
91
+ //*********************************************************************//
92
+ // --------------- public immutable stored properties ---------------- //
93
+ //*********************************************************************//
94
+
95
+ IJBDirectory public immutable override DIRECTORY;
96
+
97
+ //*********************************************************************//
98
+ // --------------------- public stored properties -------------------- //
99
+ //*********************************************************************//
100
+
101
+ //*********************************************************************//
102
+ // -------------------- internal stored properties ------------------- //
103
+ //*********************************************************************//
104
+
105
+ //*********************************************************************//
106
+ // -------------------------- constructor ---------------------------- //
107
+ //*********************************************************************//
108
+
109
+ //*********************************************************************//
110
+ // ---------------------- receive / fallback ------------------------- //
111
+ //*********************************************************************//
112
+
113
+ //*********************************************************************//
114
+ // --------------------------- modifiers ----------------------------- //
115
+ //*********************************************************************//
116
+
117
+ //*********************************************************************//
118
+ // ---------------------- external transactions ---------------------- //
119
+ //*********************************************************************//
120
+
121
+ //*********************************************************************//
122
+ // ----------------------- external views ---------------------------- //
123
+ //*********************************************************************//
124
+
125
+ //*********************************************************************//
126
+ // ----------------------- public transactions ----------------------- //
127
+ //*********************************************************************//
128
+
129
+ //*********************************************************************//
130
+ // ----------------------- internal helpers -------------------------- //
131
+ //*********************************************************************//
132
+
133
+ //*********************************************************************//
134
+ // ----------------------- internal views ---------------------------- //
135
+ //*********************************************************************//
136
+
137
+ //*********************************************************************//
138
+ // ----------------------- private helpers --------------------------- //
139
+ //*********************************************************************//
140
+ }
141
+ ```
142
+
143
+ **Section order:**
144
+ 1. `using` declarations
145
+ 2. Custom errors
146
+ 3. Public constants
147
+ 4. Internal constants
148
+ 5. Public immutable stored properties
149
+ 6. Internal immutable stored properties
150
+ 7. Public stored properties
151
+ 8. Internal stored properties
152
+ 9. Constructor
153
+ 10. `receive` / `fallback`
154
+ 11. Modifiers
155
+ 12. External transactions
156
+ 13. External views
157
+ 14. Public transactions
158
+ 15. Internal helpers
159
+ 16. Internal views
160
+ 17. Private helpers
161
+
162
+ Functions are alphabetized within each section.
163
+
164
+ **Events:** Events are declared in interfaces only, never in implementation contracts. Implementations inherit events from their interface and emit them unqualified. This keeps the ABI definition in one place and allows tests to use interface-qualified event expectations (e.g., `emit IJBController.LaunchProject(...)`).
165
+
166
+ ## Interface Structure
167
+
168
+ ```solidity
169
+ /// @notice One-line description.
170
+ interface IJBExample is IJBBase {
171
+ // Events (with full NatSpec)
172
+
173
+ /// @notice Emitted when X happens.
174
+ /// @param projectId The ID of the project.
175
+ /// @param amount The amount transferred.
176
+ event SomethingHappened(uint256 indexed projectId, uint256 amount);
177
+
178
+ // Views (alphabetized)
179
+
180
+ /// @notice The directory of terminals and controllers.
181
+ function DIRECTORY() external view returns (IJBDirectory);
182
+
183
+ // State-changing functions (alphabetized)
184
+
185
+ /// @notice Does the thing.
186
+ /// @param projectId The ID of the project.
187
+ /// @return result The result.
188
+ function doThing(uint256 projectId) external returns (uint256 result);
189
+ }
190
+ ```
191
+
192
+ **Rules:**
193
+ - Events first, then views, then state-changing functions
194
+ - No custom errors in interfaces — errors belong in the implementing contract
195
+ - Full NatSpec on every event, function, and parameter
196
+ - Alphabetized within each group
197
+
198
+ ## Naming
199
+
200
+ | Thing | Convention | Example |
201
+ |-------|-----------|---------|
202
+ | Contract | PascalCase | `JBMultiTerminal` |
203
+ | Interface | `I` + PascalCase | `IJBMultiTerminal` |
204
+ | Library | PascalCase | `JBCashOuts` |
205
+ | Struct | PascalCase | `JBRulesetConfig` |
206
+ | Enum | PascalCase | `JBApprovalStatus` |
207
+ | Enum value | PascalCase | `ApprovalExpected` |
208
+ | Error | `ContractName_ErrorName` | `JBMultiTerminal_FeeTerminalNotFound` |
209
+ | Public constant | `ALL_CAPS` | `FEE`, `MAX_FEE` |
210
+ | Internal constant | `_ALL_CAPS` | `_FEE_HOLDING_SECONDS` |
211
+ | Public immutable | `ALL_CAPS` | `DIRECTORY`, `PERMISSIONS` |
212
+ | Public/external function | `camelCase` | `cashOutTokensOf` |
213
+ | Internal/private function | `_camelCase` | `_processFee` |
214
+ | Internal storage | `_camelCase` | `_accountingContextForTokenOf` |
215
+ | Function parameter | `camelCase` | `projectId`, `cashOutCount` |
216
+
217
+ ## NatSpec
218
+
219
+ **Contracts:**
220
+ ```solidity
221
+ /// @notice One-line description of what the contract does.
222
+ contract JBExample is IJBExample {
223
+ ```
224
+
225
+ **Functions:**
226
+ ```solidity
227
+ /// @notice Records funds being added to a project's balance.
228
+ /// @param projectId The ID of the project which funds are being added to.
229
+ /// @param token The token being added.
230
+ /// @param amount The amount added, as a fixed point number with the same decimals as the terminal.
231
+ /// @return surplus The new surplus after adding.
232
+ function recordAddedBalanceFor(
233
+ uint256 projectId,
234
+ address token,
235
+ uint256 amount
236
+ ) external override returns (uint256 surplus) {
237
+ ```
238
+
239
+ **Structs:**
240
+ ```solidity
241
+ /// @custom:member duration The number of seconds the ruleset lasts for. 0 means it never expires.
242
+ /// @custom:member weight How many tokens to mint per unit paid (18 decimals).
243
+ /// @custom:member weightCutPercent How much weight decays each cycle (9 decimals).
244
+ struct JBRulesetConfig {
245
+ uint32 duration;
246
+ uint112 weight;
247
+ uint32 weightCutPercent;
248
+ }
249
+ ```
250
+
251
+ **Mappings:**
252
+ ```solidity
253
+ /// @notice Context describing how a token is accounted for by a project.
254
+ /// @custom:param projectId The ID of the project.
255
+ /// @custom:param token The address of the token.
256
+ mapping(uint256 projectId => mapping(address token => JBAccountingContext)) internal _accountingContextForTokenOf;
257
+ ```
258
+
259
+ ## Numbers
260
+
261
+ Use underscores for thousands separators:
262
+
263
+ ```solidity
264
+ uint256 internal constant _FEE_HOLDING_SECONDS = 2_419_200; // 28 days
265
+ uint32 public constant MAX_WEIGHT_CUT_PERCENT = 1_000_000_000;
266
+ uint256 public constant MAX_RESERVED_PERCENT = 10_000;
267
+ ```
268
+
269
+ ## Function Calls
270
+
271
+ Use named parameters for readability when calling functions with 3+ arguments:
272
+
273
+ ```solidity
274
+ PERMISSIONS.hasPermission({
275
+ operator: sender,
276
+ account: account,
277
+ projectId: projectId,
278
+ permissionId: permissionId,
279
+ includeRoot: true,
280
+ includeWildcardProjectId: true
281
+ });
282
+ ```
283
+
284
+ ## Multiline Signatures
285
+
286
+ ```solidity
287
+ function recordCashOutFor(
288
+ address holder,
289
+ uint256 projectId,
290
+ uint256 cashOutCount,
291
+ JBAccountingContext calldata accountingContext
292
+ )
293
+ external
294
+ override
295
+ returns (
296
+ JBRuleset memory ruleset,
297
+ uint256 reclaimAmount,
298
+ JBCashOutHookSpecification[] memory hookSpecifications
299
+ )
300
+ {
301
+ ```
302
+
303
+ Modifiers and return types go on their own indented lines.
304
+
305
+ ## Error Handling
306
+
307
+ - Validate inputs with explicit `revert` + custom error
308
+ - Use `try-catch` only for external calls to untrusted contracts (hooks, fee processing)
309
+ - Always include relevant context in error parameters
310
+
311
+ ```solidity
312
+ // Direct validation
313
+ if (amount > limit) revert JBTerminalStore_InadequateControllerPayoutLimit(amount, limit);
314
+
315
+ // External call to untrusted hook
316
+ try hook.afterPayRecordedWith(context) {} catch (bytes memory reason) {
317
+ emit HookAfterPayReverted(hook, context, reason, _msgSender());
318
+ }
319
+ ```
320
+
321
+ ---
322
+
323
+ ## DevOps
324
+
325
+ ### foundry.toml
326
+
327
+ Standard config across all repos:
328
+
329
+ ```toml
330
+ [profile.default]
331
+ solc = '0.8.26'
332
+ evm_version = 'cancun'
333
+ optimizer_runs = 200
334
+ libs = ["node_modules", "lib"]
335
+ fs_permissions = [{ access = "read-write", path = "./"}]
336
+
337
+ [profile.ci_sizes]
338
+ optimizer_runs = 200
339
+
340
+ [fuzz]
341
+ runs = 4096
342
+
343
+ [invariant]
344
+ runs = 1024
345
+ depth = 100
346
+ fail_on_revert = false
347
+
348
+ [fmt]
349
+ number_underscore = "thousands"
350
+ multiline_func_header = "all"
351
+ wrap_comments = true
352
+ ```
353
+
354
+ **Variations:**
355
+ - `evm_version = 'cancun'` for repos using transient storage (buyback-hook, router-terminal, univ4-router)
356
+ - `via_ir = true` for repos hitting stack-too-deep (buyback-hook, banny-retail, univ4-lp-split-hook, deploy-all)
357
+ - `optimizer = false` only for deploy-all-v6 (stack-too-deep with optimization)
358
+
359
+ ### CI Workflows
360
+
361
+ Every repo has at minimum `test.yml` and `lint.yml`:
362
+
363
+ **test.yml:**
364
+ ```yaml
365
+ name: test
366
+ on:
367
+ pull_request:
368
+ branches: [main]
369
+ push:
370
+ branches: [main]
371
+ jobs:
372
+ forge-test:
373
+ runs-on: ubuntu-latest
374
+ steps:
375
+ - uses: actions/checkout@v4
376
+ with:
377
+ submodules: recursive
378
+ - uses: actions/setup-node@v4
379
+ with:
380
+ node-version: 22.4.x
381
+ - name: Install npm dependencies
382
+ run: npm install --omit=dev
383
+ - name: Install Foundry
384
+ uses: foundry-rs/foundry-toolchain@v1
385
+ - name: Run tests
386
+ run: forge test --fail-fast --summary --detailed --skip "*/script/**"
387
+ - name: Check contract sizes
388
+ run: FOUNDRY_PROFILE=ci_sizes forge build --sizes --skip "*/test/**" --skip "*/script/**" --skip SphinxUtils
389
+ ```
390
+
391
+ **lint.yml:**
392
+ ```yaml
393
+ name: lint
394
+ on:
395
+ pull_request:
396
+ branches: [main]
397
+ push:
398
+ branches: [main]
399
+ jobs:
400
+ forge-fmt:
401
+ runs-on: ubuntu-latest
402
+ steps:
403
+ - uses: actions/checkout@v4
404
+ - name: Install Foundry
405
+ uses: foundry-rs/foundry-toolchain@v1
406
+ - name: Check formatting
407
+ run: forge fmt --check
408
+ ```
409
+
410
+ ### package.json
411
+
412
+ ```json
413
+ {
414
+ "name": "@bananapus/ownable-v6",
415
+ "version": "x.x.x",
416
+ "license": "MIT",
417
+ "repository": { "type": "git", "url": "git+https://github.com/Org/repo.git" },
418
+ "engines": { "node": ">=20.0.0" },
419
+ "scripts": {
420
+ "test": "forge test",
421
+ "coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary"
422
+ },
423
+ "dependencies": { ... },
424
+ "devDependencies": {
425
+ "@sphinx-labs/plugins": "^0.33.2"
426
+ }
427
+ }
428
+ ```
429
+
430
+ **Scoping:** `@bananapus/` for Bananapus repos, `@rev-net/` for revnet, `@croptop/` for croptop, `@bannynet/` for banny, `@ballkidz/` for defifa.
431
+
432
+ ### remappings.txt
433
+
434
+ Every repo has a `remappings.txt`. Minimal content:
435
+
436
+ ```
437
+ @sphinx-labs/contracts/=lib/sphinx/packages/contracts/contracts/foundry
438
+ ```
439
+
440
+ Additional mappings as needed for repo-specific dependencies.
441
+
442
+ ### Formatting
443
+
444
+ Run `forge fmt` before committing. The `[fmt]` config in `foundry.toml` enforces:
445
+ - Thousands separators on numbers (`1_000_000`)
446
+ - Multiline function headers when multiple parameters
447
+ - Wrapped comments at reasonable width
448
+
449
+ CI checks formatting via `forge fmt --check`.
450
+
451
+ ### Branching
452
+
453
+ - `main` is the primary branch
454
+ - Feature branches for PRs
455
+ - All PRs trigger test + lint workflows
456
+ - Submodule checkout with `--recursive` in CI
457
+
458
+ ### Dependencies
459
+
460
+ - Solidity dependencies via npm (`node_modules/`)
461
+ - `forge-std` as a git submodule in `lib/`
462
+ - Sphinx plugins as a devDependency
463
+ - Cross-repo references use `file:../sibling-repo` in local development
464
+ - Published versions use semver ranges (`^0.0.x`) for npm
465
+
466
+ ### Contract Size Checks
467
+
468
+ CI runs `FOUNDRY_PROFILE=ci_sizes forge build --sizes` to catch contracts approaching the 24KB limit. The `ci_sizes` profile uses `optimizer_runs = 200` for realistic size measurement even when the default profile has different optimizer settings.
package/foundry.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [profile.default]
2
2
  solc = '0.8.26'
3
- evm_version = 'paris'
3
+ evm_version = 'cancun'
4
4
  optimizer_runs = 200
5
5
  libs = ["node_modules", "lib"]
6
6
  fs_permissions = [{ access = "read-write", path = "./"}]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/ownable-v6",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,7 +10,7 @@
10
10
  "node": ">=20.0.0"
11
11
  },
12
12
  "dependencies": {
13
- "@bananapus/core-v6": "^0.0.10",
13
+ "@bananapus/core-v6": "^0.0.11",
14
14
  "@openzeppelin/contracts": "^5.0.2"
15
15
  },
16
16
  "scripts": {
@@ -12,7 +12,7 @@ import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
12
12
  import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
13
13
 
14
14
  /// @title L65_BurnLockProtection
15
- /// @notice Regression test for L-65: Verifies that if a project NFT is burned/invalidated,
15
+ /// @notice Verifies that if a project NFT is burned/invalidated,
16
16
  /// owner() returns address(0) and _checkOwner() reverts gracefully instead of
17
17
  /// permanently locking the contract with an unrecoverable revert.
18
18
  contract L65_BurnLockProtection is Test {
@@ -11,7 +11,7 @@ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.s
11
11
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
12
12
 
13
13
  /// @title L66_ZeroAddressValidation
14
- /// @notice Regression test for L-66: Verifies that deploying with a zero-address PROJECTS
14
+ /// @notice Verifies that deploying with a zero-address PROJECTS
15
15
  /// contract and a non-zero projectId reverts at construction time, preventing
16
16
  /// permanently broken project-based ownership.
17
17
  contract L66_ZeroAddressValidation is Test {