@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 +12 -7
- package/package.json +10 -10
- package/script/Drop1.s.sol +57 -34
- package/script/helpers/MigrationHelper.sol +25 -24
- package/src/Banny721TokenUriResolver.sol +19 -19
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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.
|
|
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.
|
|
31
|
-
"@bananapus/buyback-hook-v6": "^0.0.
|
|
32
|
-
"@bananapus/core-v6": "^0.0.
|
|
33
|
-
"@bananapus/router-terminal-v6": "^0.0.
|
|
34
|
-
"@bananapus/suckers-v6": "^0.0.
|
|
35
|
-
"@croptop/core-v6": "^0.0.
|
|
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.
|
|
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.
|
|
41
|
+
"@bananapus/address-registry-v6": "^0.0.29",
|
|
42
42
|
"@sphinx-labs/plugins": "0.33.3"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/script/Drop1.s.sol
CHANGED
|
@@ -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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
27
|
-
(uint256
|
|
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
|
|
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 =
|
|
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 <
|
|
42
|
-
uint256
|
|
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 (
|
|
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
|
-
|
|
58
|
+
targetBackgroundUpc == v4BackgroundUpc && targetOutfitIds.length == v4OutfitIds.length,
|
|
59
|
+
"V4/target asset mismatch"
|
|
58
60
|
);
|
|
59
61
|
|
|
60
|
-
for (uint256 i = 0; i <
|
|
61
|
-
uint256
|
|
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(
|
|
65
|
+
require(targetOutfitUpc == v4OutfitUpc, "V4/target asset mismatch");
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
/// @notice Verify that tier balances
|
|
69
|
-
/// @param hookAddress
|
|
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
|
|
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
|
|
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
|
|
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
|
|
105
|
+
uint256 targetBalance = targetStore.tierBalanceOf({hook: hookAddress, owner: owner, tierId: tierId});
|
|
104
106
|
|
|
105
|
-
// Require that
|
|
107
|
+
// Require that the target balance is never greater than the V4 balance.
|
|
106
108
|
require(
|
|
107
|
-
|
|
109
|
+
targetBalance <= v4Balance,
|
|
108
110
|
string.concat(
|
|
109
|
-
"
|
|
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
|
-
"
|
|
116
|
-
_uint256ToString(
|
|
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
|
|
69
|
-
/// @dev
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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,
|
|
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
|
|
1568
|
-
// `_attachedOutfitIdsOf`
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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`
|
|
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
|
-
//
|
|
1635
|
+
// Advance to the next previous outfit.
|
|
1636
1636
|
previousOutfitId = previousOutfitIds[previousOutfitIndex];
|
|
1637
1637
|
} else {
|
|
1638
1638
|
previousOutfitId = 0;
|