@bannynet/core-v6 0.0.40 → 0.0.42

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/README.md CHANGED
@@ -2,13 +2,18 @@
2
2
 
3
3
  Banny Retail is an onchain avatar system for Juicebox 721 collections. A body NFT can wear outfit NFTs, use a background NFT, and resolve to a base64 JSON token URI whose image is an onchain SVG.
4
4
 
5
- Docs: <https://docs.juicebox.money>
6
- Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
7
- User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
8
- Skills: [SKILLS.md](./SKILLS.md)
9
- Risks: [RISKS.md](./RISKS.md)
10
- Administration: [ADMINISTRATION.md](./ADMINISTRATION.md)
11
- Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
5
+
6
+ ## Documentation
7
+
8
+ - [ARCHITECTURE.md](./ARCHITECTURE.md) — system structure and contract responsibilities
9
+ - [USER_JOURNEYS.md](./USER_JOURNEYS.md) — end-to-end flows for body owners and asset holders
10
+ - [INVARIANTS.md](./INVARIANTS.md) — properties guaranteed by the resolver
11
+ - [RISKS.md](./RISKS.md) — known risks and edge cases
12
+ - [ADMINISTRATION.md](./ADMINISTRATION.md) — owner-only actions and operational levers
13
+ - [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — guidance for security reviewers
14
+ - [SKILLS.md](./SKILLS.md) — repo-specific gotchas and integration notes
15
+ - [STYLE_GUIDE.md](./STYLE_GUIDE.md) — coding and naming conventions
16
+ - [CHANGELOG.md](./CHANGELOG.md) — notable changes
12
17
 
13
18
  ## Overview
14
19
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@bannynet/core-v6",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/mejango/banny-retail-v6"
7
+ "url": "git+https://github.com/mejango/banny-retail-v6.git"
8
8
  },
9
9
  "files": [
10
10
  "foundry.toml",
@@ -27,18 +27,18 @@
27
27
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'banny-core-v6'"
28
28
  },
29
29
  "dependencies": {
30
- "@bananapus/721-hook-v6": "^0.0.57",
31
- "@bananapus/buyback-hook-v6": "^0.0.55",
32
- "@bananapus/core-v6": "^0.0.68",
33
- "@bananapus/router-terminal-v6": "^0.0.52",
34
- "@bananapus/suckers-v6": "^0.0.58",
35
- "@croptop/core-v6": "^0.0.59",
30
+ "@bananapus/721-hook-v6": "^0.0.59",
31
+ "@bananapus/buyback-hook-v6": "^0.0.58",
32
+ "@bananapus/core-v6": "^0.0.72",
33
+ "@bananapus/router-terminal-v6": "^0.0.55",
34
+ "@bananapus/suckers-v6": "^0.0.60",
35
+ "@croptop/core-v6": "^0.0.60",
36
36
  "@openzeppelin/contracts": "5.6.1",
37
- "@rev-net/core-v6": "^0.0.76",
37
+ "@rev-net/core-v6": "^0.0.78",
38
38
  "keccak": "3.0.4"
39
39
  },
40
40
  "devDependencies": {
41
- "@bananapus/address-registry-v6": "^0.0.28",
41
+ "@bananapus/address-registry-v6": "^0.0.29",
42
42
  "@sphinx-labs/plugins": "0.33.3"
43
43
  }
44
44
  }
@@ -50,11 +50,58 @@ contract Drop1Script is Script, Sphinx {
50
50
  }
51
51
 
52
52
  function deploy() public sphinx {
53
+ // Build the Drop 1 tier set, resolving every reserve-bearing tier's beneficiary to the configured
54
+ // `reserveBeneficiary`.
55
+ (string[] memory names, bytes32[] memory svgHashes, JB721TierConfig[] memory products) =
56
+ buildDrop1Tiers(reserveBeneficiary);
57
+
58
+ // Capture the pre-existing maxTierIdOf so we can detect drift between Sphinx proposal-time simulation and
59
+ // execution. Without this guard, an authorized `ADJUST_721_TIERS` call landing between proposal and
60
+ // execution would shift our 47 new tier IDs upward, and the metadata writes below would silently target
61
+ // the wrong UPC range (or land on tiers that did not get our SVG/name data).
62
+ uint256 maxTierIdBeforeAdjust = hook.STORE().maxTierIdOf(address(hook));
63
+
64
+ hook.adjustTiers({tiersToAdd: products, tierIdsToRemove: new uint256[](0)});
65
+
66
+ // Read maxTierIdOf after adjustTiers so the value reflects our newly added tiers,
67
+ // avoiding a race condition where another transaction could change maxTierIdOf between
68
+ // the read and the adjustTiers call.
69
+ uint256 maxTierId = hook.STORE().maxTierIdOf(address(hook));
70
+
71
+ // Drift detection: our 47 tiers should occupy exactly the range (maxTierIdBeforeAdjust, maxTierId]. If
72
+ // another transaction added tiers between proposal and execution, the range no longer matches the 47-tier
73
+ // assumption and the metadata writes would target the wrong UPCs.
74
+ require(maxTierId == maxTierIdBeforeAdjust + 47, "Drop1: maxTierIdOf drift between proposal and execution");
75
+
76
+ // Build the product IDs array for the newly added tiers.
77
+ // The last 47 tier IDs correspond to this drop's tiers.
78
+ uint256[] memory productIds = new uint256[](47);
79
+ for (uint256 i; i < 47; i++) {
80
+ productIds[i] = maxTierId - 46 + i;
81
+ }
82
+
83
+ bannyverse.resolver.setSvgHashesOf({upcs: productIds, svgHashes: svgHashes});
84
+ bannyverse.resolver.setProductNames({upcs: productIds, names: names});
85
+ }
86
+
87
+ /// @notice Builds the Drop 1 tier set: product names, SVG hashes, and tier configs.
88
+ /// @dev Tiers must be sorted by category ascending, and any tier with a non-zero `reserveFrequency` must have a
89
+ /// resolvable reserve beneficiary at add time. The first reserve-bearing tier sets the hook's default reserve
90
+ /// beneficiary (via `useReserveBeneficiaryAsDefault`) so that every later reserve-bearing tier inherits it.
91
+ /// @param reserveBeneficiary_ The address that receives reserve NFTs for every reserve-bearing tier in the drop.
92
+ /// @return names The product name for each tier, indexed to `products`.
93
+ /// @return svgHashes The SVG content hash for each tier, indexed to `products`.
94
+ /// @return products The tier configs to add.
95
+ function buildDrop1Tiers(address reserveBeneficiary_)
96
+ public
97
+ pure
98
+ returns (string[] memory names, bytes32[] memory svgHashes, JB721TierConfig[] memory products)
99
+ {
53
100
  uint256 decimals = 18;
54
101
 
55
- string[] memory names = new string[](47);
56
- bytes32[] memory svgHashes = new bytes32[](47);
57
- JB721TierConfig[] memory products = new JB721TierConfig[](47);
102
+ names = new string[](47);
103
+ svgHashes = new bytes32[](47);
104
+ products = new JB721TierConfig[](47);
58
105
 
59
106
  // Desk
60
107
  names[0] = "Work Station";
@@ -153,6 +200,10 @@ contract Drop1Script is Script, Sphinx {
153
200
  splits: new JBSplit[](0)
154
201
  });
155
202
  // Block chain
203
+ // This is the first reserve-bearing tier in the drop (sorted by category ascending), so it establishes the
204
+ // hook's default reserve beneficiary. Setting it here means every later reserve-bearing tier can leave
205
+ // `reserveBeneficiary` as `address(0)` and inherit this default at add time, and the tiers add in one pass
206
+ // without a missing-beneficiary revert.
156
207
  names[4] = "Block Chain";
157
208
  svgHashes[4] = bytes32(0x5e609d387ea091bc8884a753ddd28dd43b8ed1243b29de6e9354ef1ab109a0b9);
158
209
  products[4] = JB721TierConfig({
@@ -160,13 +211,13 @@ contract Drop1Script is Script, Sphinx {
160
211
  initialSupply: 12,
161
212
  votingUnits: 0,
162
213
  reserveFrequency: 12,
163
- reserveBeneficiary: address(0),
214
+ reserveBeneficiary: reserveBeneficiary_,
164
215
  encodedIpfsUri: bytes32(0xef6478be50575bade53e7ce4c9fb5b399643bcabed94f2111afb63e97fb9fd44),
165
216
  category: 3,
166
217
  discountPercent: 0,
167
218
  flags: JB721TierConfigFlags({
168
219
  allowOwnerMint: true,
169
- useReserveBeneficiaryAsDefault: false,
220
+ useReserveBeneficiaryAsDefault: true,
170
221
  transfersPausable: false,
171
222
  useVotingUnits: false,
172
223
  cantBeRemoved: false,
@@ -232,7 +283,7 @@ contract Drop1Script is Script, Sphinx {
232
283
  initialSupply: 100,
233
284
  votingUnits: 0,
234
285
  reserveFrequency: 25,
235
- reserveBeneficiary: reserveBeneficiary,
286
+ reserveBeneficiary: reserveBeneficiary_,
236
287
  encodedIpfsUri: bytes32(0xf01423f9dae3de4adc7e372e6902a351e2c6193a385dde90f5baf37165914831),
237
288
  category: 6,
238
289
  discountPercent: 0,
@@ -1184,33 +1235,5 @@ contract Drop1Script is Script, Sphinx {
1184
1235
  splitPercent: 0,
1185
1236
  splits: new JBSplit[](0)
1186
1237
  });
1187
-
1188
- // Capture the pre-existing maxTierIdOf so we can detect drift between Sphinx proposal-time simulation and
1189
- // execution. Without this guard, an authorized `ADJUST_721_TIERS` call landing between proposal and
1190
- // execution would shift our 47 new tier IDs upward, and the metadata writes below would silently target
1191
- // the wrong UPC range (or land on tiers that did not get our SVG/name data).
1192
- uint256 maxTierIdBeforeAdjust = hook.STORE().maxTierIdOf(address(hook));
1193
-
1194
- hook.adjustTiers({tiersToAdd: products, tierIdsToRemove: new uint256[](0)});
1195
-
1196
- // Read maxTierIdOf after adjustTiers so the value reflects our newly added tiers,
1197
- // avoiding a race condition where another transaction could change maxTierIdOf between
1198
- // the read and the adjustTiers call.
1199
- uint256 maxTierId = hook.STORE().maxTierIdOf(address(hook));
1200
-
1201
- // Drift detection: our 47 tiers should occupy exactly the range (maxTierIdBeforeAdjust, maxTierId]. If
1202
- // another transaction added tiers between proposal and execution, the range no longer matches the 47-tier
1203
- // assumption and the metadata writes would target the wrong UPCs.
1204
- require(maxTierId == maxTierIdBeforeAdjust + 47, "Drop1: maxTierIdOf drift between proposal and execution");
1205
-
1206
- // Build the product IDs array for the newly added tiers.
1207
- // The last 47 tier IDs correspond to the 47 tiers we just added.
1208
- uint256[] memory productIds = new uint256[](47);
1209
- for (uint256 i; i < 47; i++) {
1210
- productIds[i] = maxTierId - 46 + i;
1211
- }
1212
-
1213
- bannyverse.resolver.setSvgHashesOf({upcs: productIds, svgHashes: svgHashes});
1214
- bannyverse.resolver.setProductNames({upcs: productIds, names: names});
1215
1238
  }
1216
1239
  }
@@ -23,25 +23,26 @@ library MigrationHelper {
23
23
  internal
24
24
  view
25
25
  {
26
- // Get V5 asset token IDs (from V5 hook)
27
- (uint256 v5BackgroundId, uint256[] memory v5OutfitIds) =
26
+ // Get target asset token IDs from the hook being verified.
27
+ (uint256 targetBackgroundId, uint256[] memory targetOutfitIds) =
28
28
  resolver.assetIdsOf({hook: hookAddress, bannyBodyId: tokenId});
29
29
  // Get V4 asset token IDs (from V4 hook)
30
30
  (uint256 v4BackgroundId, uint256[] memory v4OutfitIds) =
31
31
  v4Resolver.assetIdsOf({hook: v4HookAddress, bannyBodyId: tokenId});
32
32
 
33
33
  // Compare background UPCs (not token IDs, since they may differ)
34
- uint256 v5BackgroundUpc = v5BackgroundId == 0 ? 0 : _getUpc({hook: hookAddress, tokenId: v5BackgroundId});
34
+ uint256 targetBackgroundUpc =
35
+ targetBackgroundId == 0 ? 0 : _getUpc({hook: hookAddress, tokenId: targetBackgroundId});
35
36
  uint256 v4BackgroundUpc = v4BackgroundId == 0 ? 0 : _getUpc({hook: v4HookAddress, tokenId: v4BackgroundId});
36
37
 
37
- bool matches = v5BackgroundUpc == v4BackgroundUpc && v5OutfitIds.length == v4OutfitIds.length;
38
+ bool matches = targetBackgroundUpc == v4BackgroundUpc && targetOutfitIds.length == v4OutfitIds.length;
38
39
 
39
40
  if (matches) {
40
41
  // Compare outfit UPCs
41
- for (uint256 i = 0; i < v5OutfitIds.length; i++) {
42
- uint256 v5OutfitUpc = _getUpc({hook: hookAddress, tokenId: v5OutfitIds[i]});
42
+ for (uint256 i = 0; i < targetOutfitIds.length; i++) {
43
+ uint256 targetOutfitUpc = _getUpc({hook: hookAddress, tokenId: targetOutfitIds[i]});
43
44
  uint256 v4OutfitUpc = _getUpc({hook: v4HookAddress, tokenId: v4OutfitIds[i]});
44
- if (v5OutfitUpc != v4OutfitUpc) {
45
+ if (targetOutfitUpc != v4OutfitUpc) {
45
46
  matches = false;
46
47
  break;
47
48
  }
@@ -54,19 +55,20 @@ library MigrationHelper {
54
55
  v4BackgroundUpc = v4BackgroundId == 0 ? 0 : _getUpc({hook: v4HookAddress, tokenId: v4BackgroundId});
55
56
 
56
57
  require(
57
- v5BackgroundUpc == v4BackgroundUpc && v5OutfitIds.length == v4OutfitIds.length, "V4/V5 asset mismatch"
58
+ targetBackgroundUpc == v4BackgroundUpc && targetOutfitIds.length == v4OutfitIds.length,
59
+ "V4/target asset mismatch"
58
60
  );
59
61
 
60
- for (uint256 i = 0; i < v5OutfitIds.length; i++) {
61
- uint256 v5OutfitUpc = _getUpc({hook: hookAddress, tokenId: v5OutfitIds[i]});
62
+ for (uint256 i = 0; i < targetOutfitIds.length; i++) {
63
+ uint256 targetOutfitUpc = _getUpc({hook: hookAddress, tokenId: targetOutfitIds[i]});
62
64
  uint256 v4OutfitUpc = _getUpc({hook: v4HookAddress, tokenId: v4OutfitIds[i]});
63
- require(v5OutfitUpc == v4OutfitUpc, "V4/V5 asset mismatch");
65
+ require(targetOutfitUpc == v4OutfitUpc, "V4/target asset mismatch");
64
66
  }
65
67
  }
66
68
  }
67
69
 
68
- /// @notice Verify that tier balances in V5 are never greater than in V4 for all owners and tiers
69
- /// @param hookAddress V5 hook address
70
+ /// @notice Verify that target tier balances are never greater than V4 for all owners and tiers
71
+ /// @param hookAddress Target hook address
70
72
  /// @param v4HookAddress V4 hook address
71
73
  /// @param v4FallbackResolverAddress V4 fallback resolver address (legacy resolver)
72
74
  /// @param owners Array of owner addresses to check
@@ -81,7 +83,7 @@ library MigrationHelper {
81
83
  internal
82
84
  view
83
85
  {
84
- IJB721TiersHookStore v5Store = JB721TiersHook(hookAddress).STORE();
86
+ IJB721TiersHookStore targetStore = JB721TiersHook(hookAddress).STORE();
85
87
  IJB721TiersHookStore v4Store = JB721TiersHook(v4HookAddress).STORE();
86
88
 
87
89
  for (uint256 i = 0; i < owners.length; i++) {
@@ -90,30 +92,30 @@ library MigrationHelper {
90
92
  for (uint256 j = 0; j < tierIds.length; j++) {
91
93
  uint256 tierId = tierIds[j];
92
94
 
93
- // Check if this tier is owned by the fallback resolver in V4
94
- // If so, skip verification (these are now owned by rightful owners in V5)
95
+ // Check if this tier is owned by the fallback resolver in V4.
96
+ // If so, skip verification because target ownership was intentionally reassigned.
95
97
  uint256 v4FallbackResolverBalance =
96
98
  v4Store.tierBalanceOf({hook: v4HookAddress, owner: v4FallbackResolverAddress, tierId: tierId});
97
99
  if (v4FallbackResolverBalance > 0) {
98
100
  continue;
99
101
  }
100
102
 
101
- // Get V4 and V5 tier balances for this owner and tier
103
+ // Get V4 and target tier balances for this owner and tier.
102
104
  uint256 v4Balance = v4Store.tierBalanceOf({hook: v4HookAddress, owner: owner, tierId: tierId});
103
- uint256 v5Balance = v5Store.tierBalanceOf({hook: hookAddress, owner: owner, tierId: tierId});
105
+ uint256 targetBalance = targetStore.tierBalanceOf({hook: hookAddress, owner: owner, tierId: tierId});
104
106
 
105
- // Require that V5 balance is never greater than V4 balance
107
+ // Require that the target balance is never greater than the V4 balance.
106
108
  require(
107
- v5Balance <= v4Balance,
109
+ targetBalance <= v4Balance,
108
110
  string.concat(
109
- "V5 tier balance exceeds V4: owner=",
111
+ "target tier balance exceeds V4: owner=",
110
112
  _addressToString(owner),
111
113
  " tier=",
112
114
  _uint256ToString(tierId),
113
115
  " v4Balance=",
114
116
  _uint256ToString(v4Balance),
115
- " v5Balance=",
116
- _uint256ToString(v5Balance)
117
+ " targetBalance=",
118
+ _uint256ToString(targetBalance)
117
119
  )
118
120
  );
119
121
  }
@@ -155,4 +157,3 @@ library MigrationHelper {
155
157
  return string(buffer);
156
158
  }
157
159
  }
158
-
@@ -65,8 +65,8 @@ contract Banny721TokenUriResolver is
65
65
  // ------------------------ private constants ------------------------ //
66
66
  //*********************************************************************//
67
67
 
68
- /// @notice Just a kind reminder to our readers.
69
- /// @dev Used in 721 token ID generation.
68
+ /// @notice The product-token ID divisor used by the 721 tiers hook.
69
+ /// @dev Token IDs are `productId * 1_000_000_000 + tokenNumber`.
70
70
  uint256 private constant _ONE_BILLION = 1_000_000_000;
71
71
 
72
72
  /// @notice The duration that banny bodies can be locked for.
@@ -100,10 +100,10 @@ contract Banny721TokenUriResolver is
100
100
  // --------------------- public stored properties -------------------- //
101
101
  //*********************************************************************//
102
102
 
103
- /// @notice The amount of time each banny body is currently locked for.
103
+ /// @notice The timestamp until which each banny body's outfit changes are locked.
104
104
  /// @custom:param hook The hook address of the collection.
105
105
  /// @custom:param bannyBodyId The ID of the banny body to lock.
106
- mapping(address hook => mapping(uint256 upc => uint256)) public override outfitLockedUntil;
106
+ mapping(address hook => mapping(uint256 bannyBodyId => uint256)) public override outfitLockedUntil;
107
107
 
108
108
  /// @notice The base of the domain hosting the SVG files that can be lazily uploaded to the contract.
109
109
  string public override svgBaseUri;
@@ -138,7 +138,7 @@ contract Banny721TokenUriResolver is
138
138
  //*********************************************************************//
139
139
 
140
140
  /// @notice The outfits currently attached to each banny body.
141
- /// @dev Naked Banny's will only be shown with outfits currently owned by the owner of the banny body.
141
+ /// @dev Naked Banny bodies are shown only with outfits currently owned by the body owner.
142
142
  /// @dev NOTE: Equipped outfits travel with the banny body NFT on transfer. When a body is transferred,
143
143
  /// the new owner inherits all equipped outfits and can unequip them to receive the outfit NFTs.
144
144
  // The _attachedOutfitIdsOf array grows with each attachment. Gas cost for operations
@@ -152,7 +152,7 @@ contract Banny721TokenUriResolver is
152
152
  mapping(address hook => mapping(uint256 bannyBodyId => uint256[])) internal _attachedOutfitIdsOf;
153
153
 
154
154
  /// @notice The background currently attached to each banny body.
155
- /// @dev Naked Banny's will only be shown with a background currently owned by the owner of the banny body.
155
+ /// @dev Naked Banny bodies are shown only with a background currently owned by the body owner.
156
156
  /// @dev NOTE: Equipped backgrounds travel with the banny body NFT on transfer, same as outfits.
157
157
  /// @custom:param hook The hook address of the collection.
158
158
  /// @custom:param bannyBodyId The ID of the banny body of the background.
@@ -226,7 +226,7 @@ contract Banny721TokenUriResolver is
226
226
  string memory extraMetadata = "";
227
227
  string memory attributes = '"attributes": [';
228
228
 
229
- // If this isn't a banny body, return the asset SVG alone (or on a manakin banny).
229
+ // If this isn't a banny body, return the asset SVG alone (or on a mannequin banny).
230
230
  if (product.category != _BODY_CATEGORY) {
231
231
  // Keep a reference to the SVG contents.
232
232
  contents = _svgOf({hook: hook, upc: product.id});
@@ -523,7 +523,7 @@ contract Banny721TokenUriResolver is
523
523
  // Get a reference to the banny body using the background.
524
524
  uint256 bannyBodyId = _userOf[hook][backgroundId];
525
525
 
526
- // If no banny body is wearing the outfit, or if its no longer the background attached, return 0.
526
+ // If no banny body is using the background, or if it's no longer attached, return 0.
527
527
  if (bannyBodyId == 0 || _attachedBackgroundIdOf[hook][bannyBodyId] != backgroundId) return 0;
528
528
 
529
529
  // Return the banny body ID.
@@ -745,7 +745,7 @@ contract Banny721TokenUriResolver is
745
745
  // Start with the item's name.
746
746
  name = string.concat(_productNameOf(product.id), " ");
747
747
 
748
- // Get just the token ID without the product ID included.
748
+ // Strip the product prefix from the token ID.
749
749
  uint256 rawTokenId = tokenId % _ONE_BILLION;
750
750
 
751
751
  string memory remainingString = " remaining";
@@ -1428,7 +1428,7 @@ contract Banny721TokenUriResolver is
1428
1428
  }
1429
1429
  }
1430
1430
 
1431
- // Effects: update state now that the old background has been successfully returned.
1431
+ // Update state after the old background has been successfully returned.
1432
1432
  _attachedBackgroundIdOf[hook][bannyBodyId] = backgroundId;
1433
1433
  _userOf[hook][backgroundId] = bannyBodyId;
1434
1434
 
@@ -1446,7 +1446,7 @@ contract Banny721TokenUriResolver is
1446
1446
  _attachedBackgroundIdOf[hook][bannyBodyId] = 0;
1447
1447
  }
1448
1448
  } else {
1449
- // No transfer needed — just clear the stale attachment record.
1449
+ // No transfer is needed; clear the stale attachment record.
1450
1450
  _attachedBackgroundIdOf[hook][bannyBodyId] = 0;
1451
1451
  }
1452
1452
  }
@@ -1493,7 +1493,7 @@ contract Banny721TokenUriResolver is
1493
1493
  previousOutfitProductCategory = _productOfTokenId({hook: hook, tokenId: previousOutfitId}).category;
1494
1494
  }
1495
1495
 
1496
- // Iterate through each outfit, transfering them in and adding them to the banny if needed, while transfering
1496
+ // Iterate through each outfit, transferring it in and adding it to the banny if needed, while transferring
1497
1497
  // out and removing old outfits no longer being worn.
1498
1498
  for (uint256 i; i < outfitIds.length;) {
1499
1499
  // Set the outfit ID being iterated on.
@@ -1564,8 +1564,8 @@ contract Banny721TokenUriResolver is
1564
1564
  // Category 0 means the tier was removed — these entries are always processed (transferred out and
1565
1565
  // skipped) so the index advances past them.
1566
1566
  while (previousOutfitId != 0 && previousOutfitProductCategory <= outfitProductCategory) {
1567
- // Transfer the previous outfit to the owner of the banny if its not being worn.
1568
- // `_attachedOutfitIdsOf` hasnt been called yet, so the wearer should still be the banny body being
1567
+ // Transfer the previous outfit to the owner of the banny if it's not being worn.
1568
+ // `_attachedOutfitIdsOf` has not been rewritten yet, so the wearer should still be the banny body being
1569
1569
  // decorated.
1570
1570
  if (previousOutfitId != outfitId && wearerOf({hook: hook, outfitId: previousOutfitId}) == bannyBodyId) {
1571
1571
  // Use try-transfer: the previous outfit may have been burned or its tier removed.
@@ -1581,9 +1581,9 @@ contract Banny721TokenUriResolver is
1581
1581
  }
1582
1582
 
1583
1583
  if (++previousOutfitIndex < previousOutfitIds.length) {
1584
- // set the next previous outfit.
1584
+ // Advance to the next previous outfit.
1585
1585
  previousOutfitId = previousOutfitIds[previousOutfitIndex];
1586
- // Get the next previous outfit.
1586
+ // Get the next previous outfit's category.
1587
1587
  previousOutfitProductCategory = _productOfTokenId({hook: hook, tokenId: previousOutfitId}).category;
1588
1588
  } else {
1589
1589
  previousOutfitId = 0;
@@ -1593,7 +1593,7 @@ contract Banny721TokenUriResolver is
1593
1593
 
1594
1594
  // If the outfit is not already being worn by the banny, transfer it to this contract.
1595
1595
  if (wearerOf({hook: hook, outfitId: outfitId}) != bannyBodyId) {
1596
- // Store the banny that's in the background.
1596
+ // Track the banny body now wearing this outfit.
1597
1597
  _wearerOf[hook][outfitId] = bannyBodyId;
1598
1598
 
1599
1599
  // Transfer the outfit to this contract.
@@ -1614,7 +1614,7 @@ contract Banny721TokenUriResolver is
1614
1614
  // attached to this banny. Since only one outfit per category is allowed, this is bounded by the number of
1615
1615
  // outfit categories (a small, fixed set).
1616
1616
  while (previousOutfitId != 0) {
1617
- // `_attachedOutfitIdsOf` hasnt been called yet, so the wearer should still be the banny body being
1617
+ // `_attachedOutfitIdsOf` has not been rewritten yet, so the wearer should still be the banny body being
1618
1618
  // decorated.
1619
1619
  // Skip outfits that are being re-equipped in the new outfit set.
1620
1620
  if (_isInArray({value: previousOutfitId, array: outfitIds})) {
@@ -1632,7 +1632,7 @@ contract Banny721TokenUriResolver is
1632
1632
  }
1633
1633
 
1634
1634
  if (++previousOutfitIndex < previousOutfitIds.length) {
1635
- // remove previous product.
1635
+ // Advance to the next previous outfit.
1636
1636
  previousOutfitId = previousOutfitIds[previousOutfitIndex];
1637
1637
  } else {
1638
1638
  previousOutfitId = 0;