@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
@@ -1,326 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
- import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
6
- import {JB721TierFlags} from "@bananapus/721-hook-v6/src/structs/JB721TierFlags.sol";
7
- import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
8
-
9
- import {Banny721TokenUriResolver} from "../src/Banny721TokenUriResolver.sol";
10
-
11
- /// @notice Minimal mock hook for attack testing.
12
- contract AttackMockHook {
13
- mapping(uint256 tokenId => address) public ownerOf;
14
- mapping(uint256 tokenId => uint32) public tierIdOf;
15
- mapping(uint256 tokenId => uint24) public categoryOf;
16
- address public immutable MOCK_STORE;
17
- mapping(address owner => mapping(address operator => bool)) public isApprovedForAll;
18
-
19
- constructor(address store) {
20
- MOCK_STORE = store;
21
- }
22
-
23
- function STORE() external view returns (address) {
24
- return MOCK_STORE;
25
- }
26
-
27
- function setOwner(uint256 tokenId, address _owner) external {
28
- ownerOf[tokenId] = _owner;
29
- }
30
-
31
- function setTier(uint256 tokenId, uint32 tierId, uint24 category) external {
32
- tierIdOf[tokenId] = tierId;
33
- categoryOf[tokenId] = category;
34
- }
35
-
36
- function setApprovalForAll(address operator, bool approved) external {
37
- isApprovedForAll[msg.sender][operator] = approved;
38
- }
39
-
40
- function safeTransferFrom(address from, address to, uint256 tokenId) external {
41
- require(
42
- msg.sender == ownerOf[tokenId] || msg.sender == from || isApprovedForAll[from][msg.sender],
43
- "MockHook: not authorized"
44
- );
45
- ownerOf[tokenId] = to;
46
-
47
- if (to.code.length > 0) {
48
- bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, "");
49
- require(retval == IERC721Receiver.onERC721Received.selector, "MockHook: receiver rejected");
50
- }
51
- }
52
-
53
- function pricingContext() external pure returns (uint256, uint256, uint256) {
54
- return (1, 18, 0);
55
- }
56
-
57
- function baseURI() external pure returns (string memory) {
58
- return "ipfs://";
59
- }
60
- }
61
-
62
- /// @notice Minimal mock store for attack testing.
63
- contract AttackMockStore {
64
- mapping(address hook => mapping(uint256 tokenId => JB721Tier)) public tiers;
65
-
66
- function setTier(address hook, uint256 tokenId, JB721Tier memory tier) external {
67
- tiers[hook][tokenId] = tier;
68
- }
69
-
70
- function tierOfTokenId(address hook, uint256 tokenId, bool) external view returns (JB721Tier memory) {
71
- return tiers[hook][tokenId];
72
- }
73
-
74
- // forge-lint: disable-next-line(mixed-case-function)
75
- function encodedTierIPFSUriOf(address, uint256) external pure returns (bytes32) {
76
- return bytes32(0);
77
- }
78
-
79
- // forge-lint: disable-next-line(mixed-case-function)
80
- function encodedIPFSUriOf(address, uint256) external pure returns (bytes32) {
81
- return bytes32(0);
82
- }
83
- }
84
-
85
- /// @title BannyAttacks
86
- /// @notice Adversarial security tests for Banny721TokenUriResolver decoration system.
87
- contract BannyAttacks is Test {
88
- Banny721TokenUriResolver resolver;
89
- AttackMockHook hook;
90
- AttackMockStore store;
91
-
92
- address deployer = makeAddr("deployer");
93
- address alice = makeAddr("alice");
94
- address bob = makeAddr("bob");
95
- address attacker = makeAddr("attacker");
96
-
97
- // Token IDs: product ID * 1_000_000_000 + sequence.
98
- // Categories: 0=Body, 1=Background, 3=Necklace, 4=Head, 5=Eyes, 7=Mouth,
99
- // 9=Suit, 10=SuitBottom, 11=SuitTop
100
- uint256 constant BODY_A = 4_000_000_001;
101
- uint256 constant BODY_B = 4_000_000_002;
102
- uint256 constant BACKGROUND = 5_000_000_001;
103
- uint256 constant NECKLACE = 10_000_000_001;
104
- uint256 constant HEAD = 20_000_000_001;
105
- uint256 constant EYES = 30_000_000_001;
106
- uint256 constant MOUTH = 40_000_000_001;
107
- uint256 constant SUIT = 50_000_000_001;
108
- uint256 constant SUIT_BOTTOM = 51_000_000_001;
109
- uint256 constant SUIT_TOP = 52_000_000_001;
110
-
111
- function setUp() public {
112
- store = new AttackMockStore();
113
- hook = new AttackMockHook(address(store));
114
-
115
- vm.prank(deployer);
116
- resolver = new Banny721TokenUriResolver(
117
- "<path/>", "<necklace/>", "<mouth/>", "<eyes/>", "<alieneyes/>", deployer, address(0)
118
- );
119
-
120
- // Set up tier data.
121
- _setupTier(BODY_A, 4, 0);
122
- _setupTier(BODY_B, 4, 0);
123
- _setupTier(BACKGROUND, 5, 1);
124
- _setupTier(NECKLACE, 10, 3);
125
- _setupTier(HEAD, 20, 4);
126
- _setupTier(EYES, 30, 5);
127
- _setupTier(MOUTH, 40, 7);
128
- _setupTier(SUIT, 50, 9);
129
- _setupTier(SUIT_BOTTOM, 51, 10);
130
- _setupTier(SUIT_TOP, 52, 11);
131
-
132
- // Give alice all tokens.
133
- hook.setOwner(BODY_A, alice);
134
- hook.setOwner(BODY_B, alice);
135
- hook.setOwner(BACKGROUND, alice);
136
- hook.setOwner(NECKLACE, alice);
137
- hook.setOwner(HEAD, alice);
138
- hook.setOwner(EYES, alice);
139
- hook.setOwner(MOUTH, alice);
140
- hook.setOwner(SUIT, alice);
141
- hook.setOwner(SUIT_BOTTOM, alice);
142
- hook.setOwner(SUIT_TOP, alice);
143
-
144
- // Approve resolver.
145
- vm.prank(alice);
146
- hook.setApprovalForAll(address(resolver), true);
147
- }
148
-
149
- function _setupTier(uint256 tokenId, uint32 tierId, uint24 category) internal {
150
- hook.setTier(tokenId, tierId, category);
151
- store.setTier(
152
- address(hook),
153
- tokenId,
154
- JB721Tier({
155
- id: tierId,
156
- price: 0,
157
- remainingSupply: 100,
158
- initialSupply: 100,
159
- votingUnits: 0,
160
- reserveFrequency: 0,
161
- reserveBeneficiary: address(0),
162
- encodedIPFSUri: bytes32(0),
163
- category: category,
164
- discountPercent: 0,
165
- flags: JB721TierFlags({
166
- allowOwnerMint: false,
167
- transfersPausable: false,
168
- cantBeRemoved: false,
169
- cantIncreaseDiscountPercent: false,
170
- cantBuyWithCredits: false
171
- }),
172
- splitPercent: 0,
173
- resolvedUri: ""
174
- })
175
- );
176
- }
177
-
178
- // =========================================================================
179
- // Test 1: Outfit reuse across body replacement
180
- // =========================================================================
181
- /// @notice Decorate body A with necklace, then try to decorate body B with
182
- /// the same necklace. The necklace should be transferred back from body A
183
- /// to the resolver, then attached to body B.
184
- function test_outfitReuse_acrossBodies() public {
185
- // Decorate body A with necklace.
186
- uint256[] memory outfitsA = new uint256[](1);
187
- outfitsA[0] = NECKLACE;
188
-
189
- vm.prank(alice);
190
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfitsA);
191
-
192
- assertEq(resolver.wearerOf(address(hook), NECKLACE), BODY_A, "Necklace on body A");
193
-
194
- // Now decorate body B with the same necklace.
195
- // Alice owns both bodies, so this should work — necklace should be removed
196
- // from body A and attached to body B.
197
- uint256[] memory outfitsB = new uint256[](1);
198
- outfitsB[0] = NECKLACE;
199
-
200
- vm.prank(alice);
201
- resolver.decorateBannyWith(address(hook), BODY_B, 0, outfitsB);
202
-
203
- assertEq(resolver.wearerOf(address(hook), NECKLACE), BODY_B, "Necklace should now be on body B");
204
- }
205
-
206
- // =========================================================================
207
- // Test 2: Lock bypass — try decorating before lock expires
208
- // =========================================================================
209
- /// @notice Lock a banny body, then try to change outfits before the lock expires.
210
- function test_lockBypass_beforeExpiry_reverts() public {
211
- // Decorate body A with necklace first.
212
- uint256[] memory outfits = new uint256[](1);
213
- outfits[0] = NECKLACE;
214
-
215
- vm.prank(alice);
216
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfits);
217
-
218
- // Lock the outfit changes.
219
- vm.prank(alice);
220
- resolver.lockOutfitChangesFor(address(hook), BODY_A);
221
-
222
- // Try to change decoration immediately — should revert.
223
- uint256[] memory newOutfits = new uint256[](1);
224
- newOutfits[0] = MOUTH;
225
-
226
- vm.prank(alice);
227
- vm.expectRevert();
228
- resolver.decorateBannyWith(address(hook), BODY_A, 0, newOutfits);
229
-
230
- // Fast-forward past lock period (7 days).
231
- vm.warp(block.timestamp + 7 days + 1);
232
-
233
- // Should succeed now.
234
- vm.prank(alice);
235
- resolver.decorateBannyWith(address(hook), BODY_A, 0, newOutfits);
236
-
237
- assertEq(resolver.wearerOf(address(hook), MOUTH), BODY_A, "Mouth should be on body A after lock");
238
- }
239
-
240
- // =========================================================================
241
- // Test 3: Category conflict — head + eyes simultaneously
242
- // =========================================================================
243
- /// @notice Head (category 4) should conflict with Eyes (category 5).
244
- function test_categoryConflict_headAndEyes_reverts() public {
245
- // Try to equip both head and eyes at once.
246
- uint256[] memory outfits = new uint256[](2);
247
- outfits[0] = HEAD; // category 4
248
- outfits[1] = EYES; // category 5
249
-
250
- vm.prank(alice);
251
- vm.expectRevert();
252
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfits);
253
- }
254
-
255
- // =========================================================================
256
- // Test 4: Category conflict — suit + suit bottom
257
- // =========================================================================
258
- /// @notice Suit (category 9) should conflict with Suit Bottom (category 10).
259
- function test_categoryConflict_suitAndParts_reverts() public {
260
- uint256[] memory outfits = new uint256[](2);
261
- outfits[0] = SUIT; // category 9
262
- outfits[1] = SUIT_BOTTOM; // category 10
263
-
264
- vm.prank(alice);
265
- vm.expectRevert();
266
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfits);
267
- }
268
-
269
- // =========================================================================
270
- // Test 5: Unauthorized decoration — non-owner tries to decorate
271
- // =========================================================================
272
- /// @notice Attacker (not the body owner) tries to decorate alice's banny.
273
- function test_unauthorizedDecoration_reverts() public {
274
- uint256[] memory outfits = new uint256[](1);
275
- outfits[0] = NECKLACE;
276
-
277
- // Give attacker a body but not the necklace owner.
278
- hook.setOwner(BODY_A, attacker);
279
-
280
- vm.prank(attacker);
281
- vm.expectRevert();
282
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfits);
283
- }
284
-
285
- // =========================================================================
286
- // Test 6: Out-of-order categories — must be ascending
287
- // =========================================================================
288
- /// @notice Outfit categories must be in ascending order. Passing mouth before necklace
289
- /// should revert.
290
- function test_outOfOrderCategories_reverts() public {
291
- uint256[] memory outfits = new uint256[](2);
292
- outfits[0] = MOUTH; // category 7
293
- outfits[1] = NECKLACE; // category 3 — out of order!
294
-
295
- vm.prank(alice);
296
- vm.expectRevert();
297
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfits);
298
- }
299
-
300
- // =========================================================================
301
- // Test 7: Body as outfit — should revert
302
- // =========================================================================
303
- /// @notice Category 0 (Body) should not be usable as an outfit.
304
- function test_bodyAsOutfit_reverts() public {
305
- uint256[] memory outfits = new uint256[](1);
306
- outfits[0] = BODY_B; // category 0
307
-
308
- vm.prank(alice);
309
- vm.expectRevert();
310
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfits);
311
- }
312
-
313
- // =========================================================================
314
- // Test 8: Background as outfit — should revert
315
- // =========================================================================
316
- /// @notice Category 1 (Background) should not be passed as outfit. It has
317
- /// its own dedicated parameter in decorateBannyWith.
318
- function test_backgroundAsOutfit_reverts() public {
319
- uint256[] memory outfits = new uint256[](1);
320
- outfits[0] = BACKGROUND; // category 1
321
-
322
- vm.prank(alice);
323
- vm.expectRevert();
324
- resolver.decorateBannyWith(address(hook), BODY_A, 0, outfits);
325
- }
326
- }