@bannynet/core-v6 0.0.24 → 0.0.26

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.
Files changed (35) hide show
  1. package/README.md +2 -2
  2. package/foundry.toml +2 -1
  3. package/package.json +22 -12
  4. package/src/Banny721TokenUriResolver.sol +10 -1
  5. package/src/interfaces/IBanny721TokenUriResolver.sol +3 -2
  6. package/ADMINISTRATION.md +0 -87
  7. package/ARCHITECTURE.md +0 -101
  8. package/AUDIT_INSTRUCTIONS.md +0 -78
  9. package/RISKS.md +0 -80
  10. package/SKILLS.md +0 -42
  11. package/STYLE_GUIDE.md +0 -610
  12. package/USER_JOURNEYS.md +0 -190
  13. package/foundry.lock +0 -14
  14. package/slither-ci.config.json +0 -10
  15. package/sphinx.lock +0 -521
  16. package/test/Banny721TokenUriResolver.t.sol +0 -694
  17. package/test/BannyAttacks.t.sol +0 -326
  18. package/test/DecorateFlow.t.sol +0 -1091
  19. package/test/Fork.t.sol +0 -2026
  20. package/test/OutfitTransferLifecycle.t.sol +0 -395
  21. package/test/TestAuditGaps.sol +0 -724
  22. package/test/TestQALastMile.t.sol +0 -447
  23. package/test/audit/AntiStrandingRetention.t.sol +0 -422
  24. package/test/audit/BurnedBodyStrandsAssets.t.sol +0 -163
  25. package/test/audit/DuplicateCategoryRetention.t.sol +0 -163
  26. package/test/audit/MergedOutfitExclusivity.t.sol +0 -228
  27. package/test/audit/MigrationHelperVerificationBypass.t.sol +0 -102
  28. package/test/audit/TryTransferFromStrandsAssets.t.sol +0 -197
  29. package/test/regression/ArrayLengthValidation.t.sol +0 -57
  30. package/test/regression/BodyCategoryValidation.t.sol +0 -147
  31. package/test/regression/BurnedTokenCheck.t.sol +0 -186
  32. package/test/regression/CEIReorder.t.sol +0 -209
  33. package/test/regression/ClearMetadata.t.sol +0 -52
  34. package/test/regression/MsgSenderEvents.t.sol +0 -153
  35. package/test/regression/RemovedTierDesync.t.sol +0 -346
package/README.md CHANGED
@@ -80,8 +80,8 @@ The contract stack relies on `via_ir = true` in `foundry.toml`.
80
80
 
81
81
  ```bash
82
82
  npm install
83
- forge build
84
- forge test
83
+ forge build --deny notes
84
+ forge test --deny notes
85
85
  ```
86
86
 
87
87
  Useful scripts:
package/foundry.toml CHANGED
@@ -15,7 +15,8 @@ depth = 100
15
15
  fail_on_revert = false
16
16
 
17
17
  [lint]
18
- exclude_lints = ["pascal-case-struct", "mixed-case-variable"]
18
+ exclude_lints = ["mixed-case-variable", "pascal-case-struct"]
19
+
19
20
  [fmt]
20
21
  number_underscore = "thousands"
21
22
  multiline_func_header = "all"
package/package.json CHANGED
@@ -1,11 +1,23 @@
1
1
  {
2
2
  "name": "@bannynet/core-v6",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/mejango/banny-retail-v6"
8
8
  },
9
+ "files": [
10
+ "CHANGELOG.md",
11
+ "foundry.toml",
12
+ "references/",
13
+ "remappings.txt",
14
+ "script/Add.Denver.s.sol",
15
+ "script/Deploy.s.sol",
16
+ "script/Drop1.s.sol",
17
+ "script/helpers/",
18
+ "script/outfit_drop/",
19
+ "src/"
20
+ ],
9
21
  "engines": {
10
22
  "node": ">=20.0.0"
11
23
  },
@@ -20,18 +32,16 @@
20
32
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'banny-core-v6'"
21
33
  },
22
34
  "dependencies": {
23
- "@bananapus/721-hook-v6": "^0.0.38",
24
- "@bananapus/core-v6": "^0.0.36",
25
- "@bananapus/permission-ids-v6": "^0.0.19",
26
- "@bananapus/router-terminal-v6": "^0.0.30",
27
- "@bananapus/suckers-v6": "^0.0.28",
28
- "@croptop/core-v6": "^0.0.36",
29
- "@openzeppelin/contracts": "^5.6.1",
30
- "@rev-net/core-v6": "^0.0.35",
31
- "keccak": "^3.0.4"
35
+ "@bananapus/721-hook-v6": "0.0.43",
36
+ "@bananapus/core-v6": "0.0.39",
37
+ "@bananapus/router-terminal-v6": "0.0.36",
38
+ "@bananapus/suckers-v6": "0.0.33",
39
+ "@openzeppelin/contracts": "5.6.1",
40
+ "@rev-net/core-v6": "0.0.39",
41
+ "keccak": "3.0.4"
32
42
  },
33
43
  "devDependencies": {
34
- "@bananapus/address-registry-v6": "^0.0.17",
35
- "@sphinx-labs/plugins": "^0.33.3"
44
+ "@bananapus/address-registry-v6": "0.0.25",
45
+ "@sphinx-labs/plugins": "0.33.3"
36
46
  }
37
47
  }
@@ -17,7 +17,10 @@ import {Base64} from "lib/base64/base64.sol";
17
17
 
18
18
  import {IBanny721TokenUriResolver} from "./interfaces/IBanny721TokenUriResolver.sol";
19
19
 
20
- /// @notice Banny asset manager. Stores and shows banny bodies in backgrounds with outfits on.
20
+ /// @notice Manages Banny NFT composition stores SVG assets (bodies, backgrounds, outfits), dresses banny bodies
21
+ /// with wearable outfits, and resolves fully on-chain SVG token URIs for the composed result. Each banny body can
22
+ /// have one background and multiple outfits attached; outfits can be locked for a duration. Asset SVGs are stored
23
+ /// on-chain and validated against pre-committed hashes.
21
24
  contract Banny721TokenUriResolver is
22
25
  Ownable,
23
26
  ERC2771Context,
@@ -291,6 +294,8 @@ contract Banny721TokenUriResolver is
291
294
 
292
295
  // If the token has an owner, check if the owner has locked the token.
293
296
  uint256 lockedUntil = outfitLockedUntil[hook][tokenId];
297
+ // Outfit locks are user-selected display locks; timestamp tolerance is acceptable here.
298
+ // forge-lint: disable-next-line(block-timestamp)
294
299
  if (lockedUntil > block.timestamp) {
295
300
  extraMetadata = string.concat(extraMetadata, '"changesLockedUntil": ', lockedUntil.toString(), ",");
296
301
  attributes = string.concat(
@@ -945,6 +950,8 @@ contract Banny721TokenUriResolver is
945
950
  /// @param bannyBodyId The body currently using the asset.
946
951
  /// @param exemptBodyId The destination body currently being decorated.
947
952
  function _revertIfBodyLocked(address hook, uint256 bannyBodyId, uint256 exemptBodyId) internal view {
953
+ // Outfit locks are user-selected display locks; timestamp tolerance is acceptable here.
954
+ // forge-lint: disable-next-line(block-timestamp)
948
955
  if (bannyBodyId != 0 && bannyBodyId != exemptBodyId && outfitLockedUntil[hook][bannyBodyId] > block.timestamp) {
949
956
  revert Banny721TokenUriResolver_OutfitChangesLocked();
950
957
  }
@@ -1118,6 +1125,8 @@ contract Banny721TokenUriResolver is
1118
1125
  }
1119
1126
 
1120
1127
  // Can't decorate a banny that's locked.
1128
+ // Outfit locks are user-selected display locks; timestamp tolerance is acceptable here.
1129
+ // forge-lint: disable-next-line(block-timestamp)
1121
1130
  if (outfitLockedUntil[hook][bannyBodyId] > block.timestamp) {
1122
1131
  revert Banny721TokenUriResolver_OutfitChangesLocked();
1123
1132
  }
@@ -1,8 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
- /// @notice Manages Banny NFT assets -- bodies, backgrounds, and outfits -- and resolves on-chain SVG token URIs for
5
- /// dressed Banny compositions.
4
+ /// @notice Manages Banny NFT assets bodies, backgrounds, and outfits and resolves on-chain SVG token URIs for
5
+ /// dressed Banny compositions. Owners dress their banny bodies with outfits and backgrounds; the resolver composes
6
+ /// all attached assets into a single SVG returned as a base64-encoded data URI.
6
7
  interface IBanny721TokenUriResolver {
7
8
  /// @notice Emitted when a banny body is decorated with a background and outfits.
8
9
  /// @param hook The hook address of the collection.
package/ADMINISTRATION.md DELETED
@@ -1,87 +0,0 @@
1
- # Administration
2
-
3
- ## At A Glance
4
-
5
- | Item | Details |
6
- | --- | --- |
7
- | Scope | `Banny721TokenUriResolver` metadata, SVG commitments, and outfit-state control |
8
- | Control posture | Global `Ownable` metadata control plus per-body owner control |
9
- | Highest-risk actions | Wrong SVG hash commitments, incorrect metadata updates, and long outfit locks |
10
- | Recovery posture | Metadata is editable, but committed hashes, uploaded SVGs, and active locks are not reversible |
11
-
12
- ## Purpose
13
-
14
- `banny-retail-v6` has a small but real control plane. The resolver owner controls collection-wide metadata and SVG commitments. Body owners control decoration and outfit locks. No admin can rescue equipped NFTs if resolver logic fails.
15
-
16
- ## Control Model
17
-
18
- - `Banny721TokenUriResolver` is `Ownable`.
19
- - Global admin power is limited to metadata, product naming, and SVG hash commitments.
20
- - Actual SVG upload is permissionless once the hash is committed.
21
- - Body owners control decoration and locking for their own bodies.
22
- - Equipped accessories are held by the resolver while attached.
23
-
24
- ## Roles
25
-
26
- | Role | How Assigned | Scope | Notes |
27
- | --- | --- | --- | --- |
28
- | Resolver owner | `Ownable(owner)` at construction | Global | Can transfer ownership with `transferOwnership()` |
29
- | Body owner | `IERC721(hook).ownerOf(bannyBodyId)` | Per body | Can decorate and lock that body |
30
- | Anyone | No assignment | Global | Can upload SVG bytes only if they match a committed hash |
31
-
32
- ## Privileged Surfaces
33
-
34
- | Contract | Function | Who Can Call | Effect |
35
- | --- | --- | --- | --- |
36
- | `Banny721TokenUriResolver` | `setMetadata(...)` | Resolver owner | Changes global description, URL, and base URI |
37
- | `Banny721TokenUriResolver` | `setProductNames(...)` | Resolver owner | Changes display names for products |
38
- | `Banny721TokenUriResolver` | `setSvgHashesOf(...)` | Resolver owner | Commits write-once SVG hashes for UPCs |
39
- | `Banny721TokenUriResolver` | `setSvgContentsOf(...)` | Anyone with matching bytes | Uploads write-once SVG payloads for committed hashes |
40
- | `Banny721TokenUriResolver` | `decorateBannyWith(...)` | Current body owner | Equips or unequips accessories and updates custody |
41
- | `Banny721TokenUriResolver` | `lockOutfitChangesFor(...)` | Current body owner | Extends the outfit lock window for that body |
42
-
43
- ## Immutable And One-Way
44
-
45
- - SVG hash commitments are write-once.
46
- - SVG contents are write-once once uploaded.
47
- - `lockOutfitChangesFor(...)` only extends the active lock.
48
- - The lock duration is fixed by `_LOCK_DURATION`.
49
- - Default art fragments, category semantics, and the trusted forwarder are constructor or code immutables.
50
-
51
- ## Operational Notes
52
-
53
- - Treat `setSvgHashesOf(...)` like a release gate. A wrong hash usually means a new resolver or new UPC strategy, not a small edit.
54
- - Treat `setMetadata(...)` and `setProductNames(...)` as collection-wide display changes.
55
- - Remind users that equipped assets are in resolver custody while attached.
56
- - Only lock outfits when temporary non-editability is the intended experience.
57
- - Use safe ERC-721 transfer flows when assets enter the resolver path. Plain `transferFrom` can strand NFTs without a recovery path.
58
-
59
- ## Machine Notes
60
-
61
- - Do not assume there is a rescue path for equipped assets. There is none.
62
- - Treat `src/Banny721TokenUriResolver.sol` as the source of truth for lock extension and write-once SVG behavior.
63
- - If a committed hash and intended asset bytes differ, stop. The contract does not support overwrite repair.
64
- - If an asset arrived through non-safe ERC-721 transfer semantics, do not assume the resolver can detect or recover it.
65
-
66
- ## Recovery
67
-
68
- - Bad metadata can be changed by the owner.
69
- - Bad SVG commitments or uploaded content cannot be corrected in place.
70
- - If equipped assets become stuck because of resolver logic, there is no owner rescue path.
71
- - If NFTs are stranded through non-safe transfer semantics, this contract does not provide recovery.
72
-
73
- ## Admin Boundaries
74
-
75
- - The owner cannot arbitrarily withdraw equipped user NFTs.
76
- - The owner cannot overwrite committed hashes or uploaded SVG contents.
77
- - The owner cannot bypass body-owner checks on decoration or locking.
78
- - Nobody can shorten an active outfit lock.
79
- - There is no pause, upgrade, or rescue mechanism.
80
-
81
- ## Source Map
82
-
83
- - `src/Banny721TokenUriResolver.sol`
84
- - `src/interfaces/IBanny721TokenUriResolver.sol`
85
- - `script/Deploy.s.sol`
86
- - `test/TestAuditGaps.sol`
87
- - `test/TestQALastMile.t.sol`
package/ARCHITECTURE.md DELETED
@@ -1,101 +0,0 @@
1
- # Architecture
2
-
3
- ## Purpose
4
-
5
- `banny-retail-v6` is the Banny-specific metadata and attachment layer for Juicebox 721 collections. It does not mint NFTs or own treasury logic. It owns attachment custody, outfit-lock rules, and final token rendering.
6
-
7
- ## System Overview
8
-
9
- The repo centers on `Banny721TokenUriResolver`. A 721 hook from `nana-721-hook-v6` points to this resolver for `tokenURI(...)`. Bodies, outfits, and backgrounds remain separate NFTs at the collection layer. The resolver escrows equipped accessories, records which assets are attached to each body, and composes the final SVG and JSON metadata on demand.
10
-
11
- ## Core Invariants
12
-
13
- - A body can only reference accessories that are currently escrowed by the resolver.
14
- - Replacing an equipped item must atomically return the old item and escrow the new item.
15
- - Outfit locks must block both explicit removal and implicit replacement until the lock expires.
16
- - Equipped assets move with the body NFT on transfer until the new owner unequips them.
17
- - Registered SVG payloads must match their committed content hash before they can render.
18
- - Rendering must stay deterministic for the same stored body state.
19
-
20
- ## Modules
21
-
22
- | Module | Responsibility | Notes |
23
- | --- | --- | --- |
24
- | `Banny721TokenUriResolver` | Escrow, attachment state, lock windows, and metadata rendering | Main contract |
25
- | `IBanny721TokenUriResolver` | External integration surface | Used by hooks and offchain tooling |
26
-
27
- ## Trust Boundaries
28
-
29
- - Minting, ownership transfer, and collection-level ERC-721 semantics live in `nana-721-hook-v6`.
30
- - This repo is trusted for rendering correctness and custody of equipped assets.
31
- - Asset-content upload is controlled by the registered content owner, but the contract verifies uploaded bytes against the stored hash.
32
-
33
- ## Critical Flows
34
-
35
- ### Decorate
36
-
37
- ```text
38
- body owner
39
- -> calls decorateBannyWith(...)
40
- -> resolver verifies body ownership and lock status
41
- -> resolver pulls new accessories into escrow
42
- -> resolver updates equipped slots
43
- -> resolver returns replaced accessories to the owner
44
- ```
45
-
46
- ### Render
47
-
48
- ```text
49
- tokenURI(bodyId)
50
- -> resolver loads body, background, and equipped slot state
51
- -> fetches registered SVG fragments
52
- -> composes layered SVG in Banny-specific order
53
- -> returns base64 JSON metadata
54
- ```
55
-
56
- ### Lock Outfit
57
-
58
- ```text
59
- body owner
60
- -> calls lockOutfitChangesFor(...)
61
- -> resolver stores a no-change window
62
- -> later decoration and removal paths must respect it
63
- ```
64
-
65
- ## Accounting Model
66
-
67
- This repo does not own treasury accounting. Its critical state is custody accounting: which NFTs are escrowed, which body they belong to, and when a body is locked against changes.
68
-
69
- That custody model uses lazy reconciliation for some stale attachment records. Read paths filter against current ownership and attachment state instead of rewriting storage on every outside transfer.
70
-
71
- ## Security Model
72
-
73
- - The main failure mode is custody drift between slot state and actual escrowed NFTs.
74
- - Rendering order is part of app semantics, not cosmetic output.
75
- - Lazy reconciliation is intentional. Changes that assume storage is always perfectly clean can strand assets or mis-render bodies.
76
- - Any new asset category adds both a rendering concern and a custody concern.
77
-
78
- ## Safe Change Guide
79
-
80
- - Keep generic ERC-721 behavior in `nana-721-hook-v6`, not here.
81
- - Review escrow writes and transfer behavior together whenever changing attachment logic.
82
- - If transfer or cleanup behavior changes, re-check lazy reconciliation alongside body-transfer inheritance.
83
- - If `tokenURI(...)` changes, test stable output for unchanged state and replacement behavior for changed state.
84
- - If adding slots or asset classes, update rendering order, slot replacement, and lock enforcement in one change.
85
-
86
- ## Canonical Checks
87
-
88
- - accessory escrow, replacement, and decoration flow:
89
- `test/DecorateFlow.t.sol`
90
- - burned-body custody edge cases:
91
- `test/audit/BurnedBodyStrandsAssets.t.sol`
92
- - transfer-path protection against stranded attachments:
93
- `test/audit/TryTransferFromStrandsAssets.t.sol`
94
-
95
- ## Source Map
96
-
97
- - `src/Banny721TokenUriResolver.sol`
98
- - `test/DecorateFlow.t.sol`
99
- - `test/audit/BurnedBodyStrandsAssets.t.sol`
100
- - `test/audit/TryTransferFromStrandsAssets.t.sol`
101
- - `script/Deploy.s.sol`
@@ -1,78 +0,0 @@
1
- # Audit Instructions
2
-
3
- This repo is the Banny avatar composition layer. It does not mint the base NFTs, but it does hold equipped accessories and define the metadata users see.
4
-
5
- ## Audit Objective
6
-
7
- Find issues that:
8
-
9
- - strand outfits or backgrounds in resolver custody
10
- - let the wrong actor equip, unequip, overwrite, or recover accessories
11
- - break outfit-lock timing or freeze a body longer than intended
12
- - return metadata that does not match stored attachment state
13
- - bypass category or layering constraints
14
-
15
- ## Scope
16
-
17
- In scope:
18
-
19
- - `src/Banny721TokenUriResolver.sol`
20
- - `src/interfaces/IBanny721TokenUriResolver.sol`
21
- - deployment helpers in `script/`
22
-
23
- ## Start Here
24
-
25
- 1. `src/Banny721TokenUriResolver.sol`
26
- 2. accessory receipt and release paths
27
- 3. deployment wiring in `script/`
28
-
29
- ## Security Model
30
-
31
- The resolver is an attachment and rendering layer around a `JB721TiersHook` collection.
32
-
33
- - the underlying 721 hook remains the token contract and source of body ownership
34
- - the resolver temporarily holds accessory NFTs while they are equipped
35
- - body ownership should be the only authority that changes equipped state
36
- - accessory contracts may be hostile or malformed, so receipt and release ordering matters
37
-
38
- ## Roles And Privileges
39
-
40
- | Role | Powers | How constrained |
41
- |------|--------|-----------------|
42
- | Body owner | Equip, unequip, and lock accessories | Must be derived from the current hook-reported owner |
43
- | Resolver owner | Update metadata and SVG-related admin state | Must not control equipped-state authorization |
44
- | Accessory NFT contract | Execute callbacks during custody changes | Must not corrupt bookkeeping or steal custody |
45
-
46
- ## Integration Assumptions
47
-
48
- | Dependency | Assumption | What breaks if wrong |
49
- |------------|------------|----------------------|
50
- | `JB721TiersHook` | Reports authentic body ownership and tier metadata | Unauthorized decoration or incorrect rendering |
51
- | Accessory ERC-721s | Behave like standard transferable NFTs | Custody or release flows fail unexpectedly |
52
-
53
- ## Critical Invariants
54
-
55
- 1. Every accessory transferred into the resolver remains attributable to one body or is recoverable by the rightful owner.
56
- 2. Only the current body owner or an intended delegate can change that body's equipped state.
57
- 3. Conflicting categories cannot be equipped together, including through replacement or invalidation edge paths.
58
- 4. Outfit-lock state only affects the intended body for the intended duration.
59
- 5. Metadata and SVG generation reflect current state and do not show impossible combinations.
60
-
61
- ## Attack Surfaces
62
-
63
- - decoration entrypoints that replace one accessory with another
64
- - ERC-721 receipt hooks and any path that accepts custody
65
- - release paths after redecorating, burning, or invalid token state
66
- - category validation and conflict checks
67
- - metadata assembly that assumes onchain assets or tier data remain available
68
-
69
- ## Accepted Risks Or Behaviors
70
-
71
- - Equipped accessories intentionally follow the body unless they are unequipped first.
72
- - Preserving attribution on failed transfer-out is safer than dropping custody state.
73
-
74
- ## Verification
75
-
76
- - `npm install`
77
- - `forge build`
78
- - `forge test`
package/RISKS.md DELETED
@@ -1,80 +0,0 @@
1
- # Banny Retail Risk Register
2
-
3
- This file focuses on the failure modes that can break NFT custody, bypass hook assumptions, or leave a rendered Banny in a state that does not match the assets users think they own.
4
-
5
- ## How to use this file
6
-
7
- - Read `Priority risks` first.
8
- - Use `Accepted Behaviors` to separate intentional tradeoffs from bugs.
9
- - Treat `Invariants to Verify` as required test and audit targets.
10
-
11
- ## Priority risks
12
-
13
- | Priority | Risk | Why it matters | Primary controls |
14
- |----------|------|----------------|------------------|
15
- | P0 | Untrusted `hook` or store integration | The resolver trusts the supplied hook for ownership checks, tier metadata, and transfers. A bad hook can fake authority or trap assets. | Restrict supported hooks, scrutinize the hook boundary, and test hostile hook behavior. |
16
- | P1 | Silent transfer-failure retention | Failed returns intentionally keep attachment records to avoid stranding NFTs, but this can leave phantom render state if the asset is gone forever. | Explicit accepted-behavior rules, retained-item handling, and custody/state invariants. |
17
- | P1 | Sale-time outfit-lock griefing | A seller can transfer a locked body and force the buyer to wait up to 7 days before changing outfits. | Fixed-duration lock, marketplace disclosure, and user education. |
18
-
19
- ## 1. Trust Assumptions
20
-
21
- - **Trusted forwarder.** ERC-2771 `_msgSender()` is trusted for ownership checks and admin functions.
22
- - **Hook contract.** The `hook` parameter is caller-supplied and not validated against a registry.
23
- - **Owner.** The contract owner controls SVG hashes, product names, and metadata URIs.
24
- - **721 hook store.** `_storeOf(hook)` trusts the hook to return a legitimate store.
25
-
26
- ## 2. Economic And Manipulation Risks
27
-
28
- - **Outfit theft via body transfer.** Equipped outfits and backgrounds move with the body NFT. If a body is sold while wearing valuable items, the buyer gains control of them.
29
- - **Try-catch silent failures with retention.** Failed transfer-outs preserve attachment records instead of clearing state. This avoids stranding but can create phantom render entries for burned or removed assets.
30
- - **Lock griefing.** `lockOutfitChangesFor` extends the lock to `block.timestamp + 7 days`. Locking just before a sale can block the buyer from changing the look for up to 7 days.
31
-
32
- ## 3. Access Control
33
-
34
- - **No hook validation.** Any address can be passed as `hook`. A malicious hook can fake `ownerOf`, execute arbitrary code during transfers, or return manipulated tier data.
35
- - **SVG content upload is permissionless once the hash is committed.** This is safe only if the committed hash is correct.
36
- - **`onERC721Received` restriction.** The contract only accepts NFTs when `operator == address(this)`. Plain `transferFrom` bypasses that and can permanently lock NFTs.
37
-
38
- ## 4. DoS Vectors
39
-
40
- - **External call iteration scales with outfit count.** `decorateBannyWith` iterates both old and new outfit arrays, so gas cost scales with the number of outfits changed in one call.
41
- - **External hook calls in view functions.** `tokenUriOf` and `svgOf` call into the hook's store multiple times. A malicious or gas-heavy hook can make metadata unreadable.
42
-
43
- ## 5. Integration Risks
44
-
45
- - **Cross-contract NFT custody.** Outfits are held by the resolver via `safeTransferFrom`. If approval is revoked on the hook contract, equipping fails.
46
- - **Tier removal desync.** If a tier is removed while an outfit from that tier is equipped, the outfit may remain attached but render empty.
47
- - **Non-safe transfer loss.** Outfits sent directly via `transferFrom` can be permanently stuck because there is no rescue function.
48
- - **Reentrancy assumptions.** `decorateBannyWith` uses `nonReentrant`, but other functions rely on ordering and limited state impact instead.
49
-
50
- ## 6. Invariants to Verify
51
-
52
- - Every outfit held by the contract has a corresponding wearer mapping to a valid body.
53
- - Every background held by the contract has a corresponding user mapping to a valid body.
54
- - `outfitLockedUntil` is monotonically non-decreasing per body.
55
- - After `decorateBannyWith`, old outfits not in the new set are either returned or explicitly retained because transfer-out failed.
56
- - Category exclusivity holds on the merged set of retained and newly supplied outfits.
57
- - SVG content integrity holds for all populated entries.
58
- - The number of held outfit NFTs should match the number of outfits still tracked as equipped for that hook.
59
-
60
- ## 7. Accepted Behaviors
61
-
62
- ### 7.1 Failed transfers retain attachment records
63
-
64
- If returning a previously equipped item fails, the resolver keeps the attachment record instead of dropping it. This avoids stranding NFTs held by the resolver, but it can leave cosmetic phantom state for permanently unrecoverable assets.
65
-
66
- ### 7.2 Lock griefing is bounded at 7 days
67
-
68
- `lockOutfitChangesFor` can force a buyer to wait, but the window is fixed and cannot be extended arbitrarily beyond the current maximum.
69
-
70
- ### 7.3 Onchain SVG rendering gas is acceptable
71
-
72
- Full `tokenUriOf` rendering is expensive but still within practical RPC limits for the supported outfit counts.
73
-
74
- ### 7.4 Outfits burn alongside the body
75
-
76
- If a body NFT is burned while outfits are equipped, those outfits are intentionally unrecoverable. Users who want to keep them must unequip first.
77
-
78
- ### 7.5 Reentrancy in non-guarded functions is treated as harmless under the current model
79
-
80
- `lockOutfitChangesFor` only extends a timestamp, and view functions do not write storage. That keeps the remaining reentrancy surface narrow.
package/SKILLS.md DELETED
@@ -1,42 +0,0 @@
1
- # Banny Retail
2
-
3
- ## Use This File For
4
-
5
- - Use this file when the task involves Banny outfit attachment, layered SVG rendering, token URI composition, or asset custody and lock behavior.
6
- - Start here, then decide whether the issue is custody state, lock timing, stored SVG content, or final token-URI composition.
7
-
8
- ## Read This Next
9
-
10
- | If you need... | Open this next |
11
- |---|---|
12
- | Repo overview and user-facing behavior | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
13
- | Resolver implementation | [`src/Banny721TokenUriResolver.sol`](./src/Banny721TokenUriResolver.sol) |
14
- | Runtime and content-management invariants | [`references/runtime.md`](./references/runtime.md), [`references/operations.md`](./references/operations.md) |
15
- | Deployment or scripted drops | [`script/Deploy.s.sol`](./script/Deploy.s.sol), [`script/Drop1.s.sol`](./script/Drop1.s.sol), [`script/Add.Denver.s.sol`](./script/Add.Denver.s.sol) |
16
- | Decoration lifecycle and custody invariants | [`test/DecorateFlow.t.sol`](./test/DecorateFlow.t.sol), [`test/OutfitTransferLifecycle.t.sol`](./test/OutfitTransferLifecycle.t.sol) |
17
- | Adversarial, fork, or final QA coverage | [`test/BannyAttacks.t.sol`](./test/BannyAttacks.t.sol), [`test/Fork.t.sol`](./test/Fork.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol), [`test/TestQALastMile.t.sol`](./test/TestQALastMile.t.sol) |
18
-
19
- ## Repo Map
20
-
21
- | Area | Where to look |
22
- |---|---|
23
- | Main contract | [`src/Banny721TokenUriResolver.sol`](./src/Banny721TokenUriResolver.sol) |
24
- | Scripts | [`script/`](./script/) |
25
- | Tests | [`test/`](./test/) |
26
-
27
- ## Purpose
28
-
29
- App-layer token URI resolver for Juicebox 721 collections. It lets Banny body NFTs equip outfit and background NFTs, holds them while equipped, and renders fully onchain layered SVG metadata.
30
-
31
- ## Reference Files
32
-
33
- - Open [`references/runtime.md`](./references/runtime.md) for attachment and custody behavior, rendering order, and the main invariants that protect equipped assets.
34
- - Open [`references/operations.md`](./references/operations.md) for upload and metadata-management behavior, deployment breadcrumbs, and common stale-data traps around SVG content.
35
-
36
- ## Working Rules
37
-
38
- - Start in [`src/Banny721TokenUriResolver.sol`](./src/Banny721TokenUriResolver.sol) for both rendering and attachment behavior.
39
- - Treat custody, stale attachment cleanup, and lock timing as high-risk. Rendering bugs are visible, but custody bugs are worse.
40
- - Equipped outfits and backgrounds travel with the body NFT. Treat that inheritance as intentional before calling it a bug.
41
- - When a task mentions minting, pricing, or terminal accounting, verify that the problem is not actually in the upstream 721 hook repo.
42
- - If you touch SVG or metadata behavior, check whether the issue is in stored content, rendering composition, or the hook-to-resolver integration point before patching.