@bananapus/ownable-v6 0.0.15 → 0.0.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/ownable-v6",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,8 +10,8 @@
10
10
  "node": ">=20.0.0"
11
11
  },
12
12
  "dependencies": {
13
- "@bananapus/core-v6": "^0.0.28",
14
- "@bananapus/permission-ids-v6": "^0.0.14",
13
+ "@bananapus/core-v6": "^0.0.31",
14
+ "@bananapus/permission-ids-v6": "^0.0.15",
15
15
  "@openzeppelin/contracts": "^5.6.1"
16
16
  },
17
17
  "scripts": {
package/src/JBOwnable.sol CHANGED
@@ -59,8 +59,8 @@ contract JBOwnable is JBOwnableOverrides {
59
59
  /// @dev This function exists because some contracts need to deploy contracts for a project before the project's NFT
60
60
  /// has been minted, so the transfer event resolves the project's current owner at emission time.
61
61
  /// @dev Unlike `_transferOwnership` (which uses try-catch to resolve the *old* owner in case its project NFT was
62
- /// burned), this function intentionally lets `PROJECTS.ownerOf(newProjectId)` revert if the new project doesn't
63
- /// exist. A revert here is desirable it prevents transferring ownership to a non-existent project.
62
+ /// burned), this function resolves the *new* owner's current address for event purposes only.
63
+ /// If the new project NFT does not exist yet, the event uses `address(0)` until ownership can resolve normally.
64
64
  function _emitTransferEvent(
65
65
  address previousOwner,
66
66
  address newOwner,
@@ -70,10 +70,17 @@ contract JBOwnable is JBOwnableOverrides {
70
70
  virtual
71
71
  override
72
72
  {
73
- emit OwnershipTransferred({
74
- previousOwner: previousOwner,
75
- newOwner: newProjectId == 0 ? newOwner : PROJECTS.ownerOf(newProjectId),
76
- caller: _msgSender()
77
- });
73
+ address resolvedNewOwner = newOwner;
74
+ if (newProjectId != 0) {
75
+ try PROJECTS.ownerOf(newProjectId) returns (address projectOwner) {
76
+ resolvedNewOwner = projectOwner;
77
+ } catch {
78
+ // Allow constructor-time handoff to an unminted project. Ownership resolves dynamically
79
+ // once the project NFT exists, so the transfer event uses address(0) until then.
80
+ resolvedNewOwner = address(0);
81
+ }
82
+ }
83
+
84
+ emit OwnershipTransferred({previousOwner: previousOwner, newOwner: resolvedNewOwner, caller: _msgSender()});
78
85
  }
79
86
  }
@@ -292,7 +292,23 @@ contract OwnableEdgeCases is Test {
292
292
  }
293
293
 
294
294
  // =========================================================================
295
- // Test 10: Fuzz transfer to any valid project, verify owner resolution
295
+ // Test 10: Constructor tolerates an unminted project owner
296
+ // =========================================================================
297
+ function test_constructorWithUnmintedProject_emitsZeroOwnerUntilMinted() public {
298
+ vm.expectEmit(true, true, false, true);
299
+ emit IJBOwnable.OwnershipTransferred(address(0), address(0), address(this));
300
+
301
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(1));
302
+
303
+ assertEq(ownable.owner(), address(0), "Owner should resolve to zero before the project exists");
304
+
305
+ uint256 projectId = projects.createFor(alice);
306
+ assertEq(projectId, 1, "Expected the first minted project to match the configured future owner");
307
+ assertEq(ownable.owner(), alice, "Owner should resolve once the project is minted");
308
+ }
309
+
310
+ // =========================================================================
311
+ // Test 11: Fuzz — transfer to any valid project, verify owner resolution
296
312
  // =========================================================================
297
313
  function testFuzz_transferToProject(address projectOwner) public isNotContract(projectOwner) {
298
314
  vm.assume(projectOwner != address(0));
@@ -313,7 +329,7 @@ contract OwnableEdgeCases is Test {
313
329
  }
314
330
 
315
331
  // =========================================================================
316
- // Test 11: Renounced contract cannot reclaim ownership
332
+ // Test 12: Renounced contract cannot reclaim ownership
317
333
  // =========================================================================
318
334
  /// @notice After renouncing, no one can call transferOwnership, transferOwnershipToProject,
319
335
  /// setPermissionId, or renounceOwnership again.
@@ -350,7 +366,7 @@ contract OwnableEdgeCases is Test {
350
366
  }
351
367
 
352
368
  // =========================================================================
353
- // Test 12: _msgSender is NOT ERC2771-aware (design documentation)
369
+ // Test 13: _msgSender is NOT ERC2771-aware (design documentation)
354
370
  // =========================================================================
355
371
  /// @notice JBOwnable uses plain Context._msgSender() (returns msg.sender),
356
372
  /// NOT ERC2771Context. This test documents that a trusted forwarder
@@ -374,7 +390,7 @@ contract OwnableEdgeCases is Test {
374
390
  }
375
391
 
376
392
  // =========================================================================
377
- // Test 13: OwnershipTransferred event uses _msgSender() (L-27 fix)
393
+ // Test 14: OwnershipTransferred event uses _msgSender() (L-27 fix)
378
394
  // =========================================================================
379
395
  /// @notice When a subclass overrides _msgSender() (e.g., for ERC-2771),
380
396
  /// the OwnershipTransferred event's caller field should reflect the
@@ -397,7 +413,7 @@ contract OwnableEdgeCases is Test {
397
413
  }
398
414
 
399
415
  // =========================================================================
400
- // Test 14: PermissionIdChanged event uses _msgSender() (L-27 fix)
416
+ // Test 15: PermissionIdChanged event uses _msgSender() (L-27 fix)
401
417
  // =========================================================================
402
418
  /// @notice When a subclass overrides _msgSender() (e.g., for ERC-2771),
403
419
  /// the PermissionIdChanged event's caller field should reflect the