@croptop/core-v6 0.0.38 → 0.0.40
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 +2 -2
- package/foundry.toml +2 -1
- package/package.json +25 -13
- package/script/ConfigureFeeProject.s.sol +8 -5
- package/src/CTDeployer.sol +67 -58
- package/src/CTProjectOwner.sol +6 -4
- package/src/CTPublisher.sol +14 -4
- package/src/interfaces/ICTDeployer.sol +2 -2
- package/src/structs/CTProjectConfig.sol +7 -6
- package/ADMINISTRATION.md +0 -94
- package/ARCHITECTURE.md +0 -96
- package/AUDIT_INSTRUCTIONS.md +0 -88
- package/RISKS.md +0 -78
- package/SKILLS.md +0 -46
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -134
- package/foundry.lock +0 -11
- package/slither-ci.config.json +0 -10
- package/sphinx.lock +0 -507
- package/test/CTDeployer.t.sol +0 -616
- package/test/CTProjectOwner.t.sol +0 -185
- package/test/CTPublisher.t.sol +0 -869
- package/test/ClaimCollectionOwnership.t.sol +0 -315
- package/test/CroptopAttacks.t.sol +0 -437
- package/test/Fork.t.sol +0 -227
- package/test/TestAuditGaps.sol +0 -696
- package/test/Test_MetadataGeneration.t.sol +0 -79
- package/test/audit/CodexNemesisCroptopPublisherBoundary.t.sol +0 -329
- package/test/audit/CodexNemesisCurrencyPoCs.t.sol +0 -371
- package/test/audit/CodexNemesisFreshRound.t.sol +0 -395
- package/test/audit/CodexNemesisMetadataShadow.t.sol +0 -196
- package/test/audit/CodexNemesisPoCs.t.sol +0 -263
- package/test/audit/CodexNemesisPolicyReuse.t.sol +0 -168
- package/test/audit/CodexNemesisUriDrift.t.sol +0 -252
- package/test/audit/DeployerPermissionBypass.t.sol +0 -213
- package/test/audit/EmptyPostFeeBypass.t.sol +0 -53
- package/test/audit/FeeBeneficiaryReentrancy.t.sol +0 -247
- package/test/audit/FeeFallbackBlackhole.t.sol +0 -263
- package/test/audit/Pass12Fixes.t.sol +0 -388
- package/test/fork/PublishFork.t.sol +0 -440
- package/test/regression/DuplicateUriFeeEvasion.t.sol +0 -312
- package/test/regression/FeeEvasion.t.sol +0 -286
- package/test/regression/StaleTierIdMapping.t.sol +0 -228
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.17;
|
|
3
|
-
|
|
4
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
-
import "forge-std/Test.sol";
|
|
6
|
-
import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
|
|
7
|
-
|
|
8
|
-
import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
|
|
9
|
-
|
|
10
|
-
/// @notice Quick test to assert the creation of metadata while minting
|
|
11
|
-
/// @dev This test is not meant to be exhaustive, but to ensure that the metadata is valid.
|
|
12
|
-
/// It uses a mock contract which only returns a metadata following the logic
|
|
13
|
-
/// of the CroptopPublisher contract during mint. This external contract is used to recreate the same
|
|
14
|
-
contract Test_MetadataGeneration_Unit is Test {
|
|
15
|
-
/// @notice Create a new metadata from the _additionalPayMetadata and the datahook metadata (containing the tiers to
|
|
16
|
-
/// mint).
|
|
17
|
-
/// @dev Naming follows CroptopPublisher contract.
|
|
18
|
-
function test_metadataBuilding() public {
|
|
19
|
-
MetadataResolverHelper _resolverHelper = new MetadataResolverHelper();
|
|
20
|
-
|
|
21
|
-
// The intial metadata passed to the terminal
|
|
22
|
-
bytes4[] memory _ids = new bytes4[](10);
|
|
23
|
-
bytes[] memory _datas = new bytes[](10);
|
|
24
|
-
|
|
25
|
-
for (uint256 _i; _i < _ids.length; _i++) {
|
|
26
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
27
|
-
_ids[_i] = bytes4(uint32(_i + 1 * 1000));
|
|
28
|
-
_datas[_i] = abi.encode(
|
|
29
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
30
|
-
bytes1(uint8(_i + 1)),
|
|
31
|
-
uint32(69),
|
|
32
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
33
|
-
bytes2(uint16(_i + 69)),
|
|
34
|
-
bytes32(uint256(type(uint256).max))
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
bytes memory _additionalPayMetadata = _resolverHelper.createMetadata(_ids, _datas);
|
|
39
|
-
|
|
40
|
-
// The referal to include in the first 32 bytes of the metadata
|
|
41
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
42
|
-
uint256 FEE_PROJECT_ID = 420;
|
|
43
|
-
|
|
44
|
-
// The additional metadata to include
|
|
45
|
-
bytes4 datahookId = bytes4(bytes20(address(0xdeadbeef)));
|
|
46
|
-
uint256[] memory tierIdsToMint = new uint256[](9);
|
|
47
|
-
|
|
48
|
-
for (uint256 i = 0; i < 9; i++) {
|
|
49
|
-
tierIdsToMint[i] = i + 1;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Test: create the new metadata:
|
|
53
|
-
bytes memory mintMetadata = JBMetadataResolver.addToMetadata({
|
|
54
|
-
originalMetadata: _additionalPayMetadata, idToAdd: datahookId, dataToAdd: abi.encode(true, tierIdsToMint)
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Add the referal id in the first 32 bytes
|
|
58
|
-
assembly {
|
|
59
|
-
mstore(add(mintMetadata, 32), FEE_PROJECT_ID)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
bytes memory targetData;
|
|
63
|
-
bool found;
|
|
64
|
-
|
|
65
|
-
// Check: both data are present and correct?
|
|
66
|
-
for (uint256 i = 0; i < _ids.length; i++) {
|
|
67
|
-
(found, targetData) = JBMetadataResolver.getDataFor(_ids[i], mintMetadata);
|
|
68
|
-
assertTrue(found, "metadata not found");
|
|
69
|
-
assertEq(targetData, _datas[i], "metadata not equal");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
(found, targetData) = JBMetadataResolver.getDataFor(datahookId, mintMetadata);
|
|
73
|
-
assertTrue(found, "datahook metadata not found");
|
|
74
|
-
assertEq(targetData, abi.encode(true, tierIdsToMint), "datahook not equal");
|
|
75
|
-
|
|
76
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
77
|
-
assertEq(uint256(bytes32(mintMetadata)), FEE_PROJECT_ID, "referal id not equal");
|
|
78
|
-
}
|
|
79
|
-
}
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import "forge-std/Test.sol";
|
|
5
|
-
|
|
6
|
-
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
7
|
-
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
8
|
-
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
9
|
-
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
10
|
-
import {JB721TierFlags} from "@bananapus/721-hook-v6/src/structs/JB721TierFlags.sol";
|
|
11
|
-
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
12
|
-
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
13
|
-
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
14
|
-
import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
|
|
15
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
16
|
-
|
|
17
|
-
import {CTAllowedPost} from "../../src/structs/CTAllowedPost.sol";
|
|
18
|
-
import {CTPost} from "../../src/structs/CTPost.sol";
|
|
19
|
-
import {CTPublisher} from "../../src/CTPublisher.sol";
|
|
20
|
-
|
|
21
|
-
contract NemesisMockPermissions is IJBPermissions {
|
|
22
|
-
function WILDCARD_PROJECT_ID() external pure returns (uint256) {
|
|
23
|
-
return 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function hasPermission(address, address, uint256, uint256, bool, bool) external pure returns (bool) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function hasPermissions(address, address, uint256, uint256[] calldata, bool, bool) external pure returns (bool) {
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function permissionsOf(address, address, uint256) external pure returns (uint256) {
|
|
35
|
-
return 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function setPermissionsFor(address, JBPermissionsData calldata) external {}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
contract NemesisMockTerminal {
|
|
42
|
-
mapping(uint256 projectId => uint256 amount) public paidToProject;
|
|
43
|
-
|
|
44
|
-
function pay(
|
|
45
|
-
uint256 projectId,
|
|
46
|
-
address,
|
|
47
|
-
uint256,
|
|
48
|
-
address,
|
|
49
|
-
uint256,
|
|
50
|
-
string calldata,
|
|
51
|
-
bytes calldata
|
|
52
|
-
)
|
|
53
|
-
external
|
|
54
|
-
payable
|
|
55
|
-
returns (uint256)
|
|
56
|
-
{
|
|
57
|
-
paidToProject[projectId] += msg.value;
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
contract NemesisMockDirectory {
|
|
63
|
-
mapping(uint256 projectId => address terminal) public terminalOf;
|
|
64
|
-
|
|
65
|
-
function setTerminal(uint256 projectId, address terminal) external {
|
|
66
|
-
terminalOf[projectId] = terminal;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function primaryTerminalOf(uint256 projectId, address) external view returns (IJBTerminal) {
|
|
70
|
-
return IJBTerminal(terminalOf[projectId]);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
contract NemesisMockStore {
|
|
75
|
-
struct StoredTier {
|
|
76
|
-
uint104 price;
|
|
77
|
-
uint32 initialSupply;
|
|
78
|
-
uint32 remainingSupply;
|
|
79
|
-
bytes32 encodedIPFSUri;
|
|
80
|
-
bool removed;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
uint256 public maxTierId;
|
|
84
|
-
mapping(uint256 tierId => StoredTier) public tierData;
|
|
85
|
-
|
|
86
|
-
function encodedUriOf(uint256 tierId) external view returns (bytes32) {
|
|
87
|
-
return tierData[tierId].encodedIPFSUri;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function addTier(JB721TierConfig memory config) external returns (uint256 tierId) {
|
|
91
|
-
tierId = ++maxTierId;
|
|
92
|
-
tierData[tierId] = StoredTier({
|
|
93
|
-
price: config.price,
|
|
94
|
-
initialSupply: config.initialSupply,
|
|
95
|
-
remainingSupply: config.initialSupply,
|
|
96
|
-
encodedIPFSUri: config.encodedIPFSUri,
|
|
97
|
-
removed: false
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function setEncodedUri(uint256 tierId, bytes32 encodedIPFSUri) external {
|
|
102
|
-
tierData[tierId].encodedIPFSUri = encodedIPFSUri;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function maxTierIdOf(address) external view returns (uint256) {
|
|
106
|
-
return maxTierId;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function isTierRemoved(address, uint256 tierId) external view returns (bool) {
|
|
110
|
-
return tierData[tierId].removed;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function tierOf(address, uint256 tierId, bool) external view returns (JB721Tier memory tier) {
|
|
114
|
-
StoredTier memory stored = tierData[tierId];
|
|
115
|
-
tier = JB721Tier({
|
|
116
|
-
id: uint32(tierId),
|
|
117
|
-
price: stored.price,
|
|
118
|
-
remainingSupply: stored.remainingSupply,
|
|
119
|
-
initialSupply: stored.initialSupply,
|
|
120
|
-
votingUnits: 0,
|
|
121
|
-
reserveFrequency: 0,
|
|
122
|
-
reserveBeneficiary: address(0),
|
|
123
|
-
encodedIPFSUri: stored.encodedIPFSUri,
|
|
124
|
-
category: 0,
|
|
125
|
-
discountPercent: 0,
|
|
126
|
-
flags: JB721TierFlags({
|
|
127
|
-
allowOwnerMint: false,
|
|
128
|
-
transfersPausable: false,
|
|
129
|
-
cantBeRemoved: false,
|
|
130
|
-
cantIncreaseDiscountPercent: false,
|
|
131
|
-
cantBuyWithCredits: false
|
|
132
|
-
}),
|
|
133
|
-
splitPercent: 0,
|
|
134
|
-
resolvedUri: ""
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
contract NemesisMutableHook {
|
|
140
|
-
uint256 public immutable PROJECT_ID;
|
|
141
|
-
IJB721TiersHookStore public immutable STORE;
|
|
142
|
-
address public ownerAddress;
|
|
143
|
-
|
|
144
|
-
constructor(uint256 projectId, IJB721TiersHookStore store_, address owner_) {
|
|
145
|
-
PROJECT_ID = projectId;
|
|
146
|
-
STORE = store_;
|
|
147
|
-
ownerAddress = owner_;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function owner() external view returns (address) {
|
|
151
|
-
return ownerAddress;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function METADATA_ID_TARGET() external view returns (address) {
|
|
155
|
-
return address(this);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function adjustTiers(JB721TierConfig[] calldata tiersToAdd, uint256[] calldata) external {
|
|
159
|
-
for (uint256 i; i < tiersToAdd.length; i++) {
|
|
160
|
-
NemesisMockStore(address(STORE)).addTier(tiersToAdd[i]);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function setMetadata(
|
|
165
|
-
string calldata,
|
|
166
|
-
string calldata,
|
|
167
|
-
string calldata,
|
|
168
|
-
string calldata,
|
|
169
|
-
address,
|
|
170
|
-
uint256 encodedIPFSUriTierId,
|
|
171
|
-
bytes32 encodedIPFSUri
|
|
172
|
-
)
|
|
173
|
-
external
|
|
174
|
-
{
|
|
175
|
-
NemesisMockStore(address(STORE)).setEncodedUri(encodedIPFSUriTierId, encodedIPFSUri);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
contract CodexNemesisCroptopPublisherBoundaryTest is Test {
|
|
180
|
-
uint256 internal constant FEE_PROJECT_ID = 1;
|
|
181
|
-
uint256 internal constant PROJECT_ID = 2;
|
|
182
|
-
|
|
183
|
-
bytes32 internal constant URI_A = keccak256("uri-a");
|
|
184
|
-
bytes32 internal constant URI_B = keccak256("uri-b");
|
|
185
|
-
|
|
186
|
-
address internal hookOwner = makeAddr("hookOwner");
|
|
187
|
-
address internal unrestrictedPoster = makeAddr("unrestrictedPoster");
|
|
188
|
-
address internal restrictedPoster = makeAddr("restrictedPoster");
|
|
189
|
-
address internal outsider = makeAddr("outsider");
|
|
190
|
-
|
|
191
|
-
NemesisMockPermissions internal permissions;
|
|
192
|
-
NemesisMockDirectory internal directory;
|
|
193
|
-
NemesisMockStore internal store;
|
|
194
|
-
NemesisMutableHook internal hook;
|
|
195
|
-
NemesisMockTerminal internal projectTerminal;
|
|
196
|
-
NemesisMockTerminal internal feeTerminal;
|
|
197
|
-
CTPublisher internal publisher;
|
|
198
|
-
|
|
199
|
-
function setUp() public {
|
|
200
|
-
permissions = new NemesisMockPermissions();
|
|
201
|
-
directory = new NemesisMockDirectory();
|
|
202
|
-
store = new NemesisMockStore();
|
|
203
|
-
hook = new NemesisMutableHook(PROJECT_ID, IJB721TiersHookStore(address(store)), hookOwner);
|
|
204
|
-
projectTerminal = new NemesisMockTerminal();
|
|
205
|
-
feeTerminal = new NemesisMockTerminal();
|
|
206
|
-
publisher = new CTPublisher(IJBDirectory(address(directory)), permissions, FEE_PROJECT_ID, address(0));
|
|
207
|
-
|
|
208
|
-
directory.setTerminal(PROJECT_ID, address(projectTerminal));
|
|
209
|
-
directory.setTerminal(FEE_PROJECT_ID, address(feeTerminal));
|
|
210
|
-
|
|
211
|
-
vm.deal(unrestrictedPoster, 100 ether);
|
|
212
|
-
vm.deal(restrictedPoster, 100 ether);
|
|
213
|
-
vm.deal(outsider, 100 ether);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function test_existingTierReuseBypassesUpdatedAllowlistAndPriceFloor() external {
|
|
217
|
-
_configureCategory(1, 1 ether, _singletonArray(unrestrictedPoster));
|
|
218
|
-
|
|
219
|
-
vm.prank(unrestrictedPoster);
|
|
220
|
-
publisher.mintFrom{value: 2 ether}(
|
|
221
|
-
IJB721TiersHook(address(hook)),
|
|
222
|
-
_singlePost({uri: URI_A, price: 1 ether, category: 1}),
|
|
223
|
-
unrestrictedPoster,
|
|
224
|
-
unrestrictedPoster,
|
|
225
|
-
"",
|
|
226
|
-
""
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
assertEq(publisher.tierIdForEncodedIPFSUriOf(address(hook), URI_A), 1, "initial publish should cache tier 1");
|
|
230
|
-
|
|
231
|
-
// Tighten the policy so only `restrictedPoster` can publish, and only at >= 5 ether.
|
|
232
|
-
_configureCategory(1, 5 ether, _singletonArray(restrictedPoster));
|
|
233
|
-
|
|
234
|
-
vm.prank(outsider);
|
|
235
|
-
publisher.mintFrom{value: 2 ether}(
|
|
236
|
-
IJB721TiersHook(address(hook)), _singlePost({uri: URI_A, price: 0, category: 1}), outsider, outsider, "", ""
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// The outsider's second call succeeds because existing-tier reuse skips the allowlist and price checks.
|
|
240
|
-
assertEq(store.maxTierId(), 1, "reuse path should mint from the old tier instead of creating a new one");
|
|
241
|
-
assertEq(
|
|
242
|
-
projectTerminal.paidToProject(PROJECT_ID),
|
|
243
|
-
3.9 ether,
|
|
244
|
-
"both mints should settle against the stale reused tier price"
|
|
245
|
-
);
|
|
246
|
-
assertEq(
|
|
247
|
-
feeTerminal.paidToProject(FEE_PROJECT_ID),
|
|
248
|
-
0.1 ether,
|
|
249
|
-
"fee routing still uses the stale reused tier price instead of the new stricter floor"
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function test_hookMetadataMutationDesyncsPublisherCacheAndAllowsDuplicateTier() external {
|
|
254
|
-
_configureCategory(1, 1 ether, new address[](0));
|
|
255
|
-
|
|
256
|
-
vm.prank(unrestrictedPoster);
|
|
257
|
-
publisher.mintFrom{value: 2 ether}(
|
|
258
|
-
IJB721TiersHook(address(hook)),
|
|
259
|
-
_singlePost({uri: URI_A, price: 1 ether, category: 1}),
|
|
260
|
-
unrestrictedPoster,
|
|
261
|
-
unrestrictedPoster,
|
|
262
|
-
"",
|
|
263
|
-
""
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
assertEq(publisher.tierIdForEncodedIPFSUriOf(address(hook), URI_A), 1, "publisher cache should point at tier 1");
|
|
267
|
-
assertEq(store.encodedUriOf(1), URI_A, "canonical hook metadata should start at uri A");
|
|
268
|
-
|
|
269
|
-
// The hook owner changes the canonical tier URI through the underlying 721 hook.
|
|
270
|
-
vm.prank(hookOwner);
|
|
271
|
-
hook.setMetadata("", "", "", "", address(0), 1, URI_B);
|
|
272
|
-
|
|
273
|
-
assertEq(store.encodedUriOf(1), URI_B, "hook metadata now says tier 1 is uri B");
|
|
274
|
-
assertEq(
|
|
275
|
-
publisher.tierIdForEncodedIPFSUriOf(address(hook), URI_A),
|
|
276
|
-
1,
|
|
277
|
-
"publisher cache is stale and still thinks uri A owns tier 1"
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
vm.prank(unrestrictedPoster);
|
|
281
|
-
publisher.mintFrom{value: 2 ether}(
|
|
282
|
-
IJB721TiersHook(address(hook)),
|
|
283
|
-
_singlePost({uri: URI_B, price: 1 ether, category: 1}),
|
|
284
|
-
unrestrictedPoster,
|
|
285
|
-
unrestrictedPoster,
|
|
286
|
-
"",
|
|
287
|
-
""
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
// Croptop creates a second tier for the same canonical URI because it never re-syncs against hook metadata.
|
|
291
|
-
assertEq(store.maxTierId(), 2, "publisher should have created a duplicate tier after the metadata drift");
|
|
292
|
-
assertEq(store.encodedUriOf(1), URI_B, "tier 1 still resolves to uri B");
|
|
293
|
-
assertEq(store.encodedUriOf(2), URI_B, "tier 2 now also resolves to uri B");
|
|
294
|
-
assertEq(publisher.tierIdForEncodedIPFSUriOf(address(hook), URI_B), 2, "cache now points uri B at tier 2");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function _configureCategory(uint24 category, uint104 minimumPrice, address[] memory allowedAddresses) internal {
|
|
298
|
-
CTAllowedPost[] memory allowedPosts = new CTAllowedPost[](1);
|
|
299
|
-
allowedPosts[0] = CTAllowedPost({
|
|
300
|
-
hook: address(hook),
|
|
301
|
-
category: category,
|
|
302
|
-
minimumPrice: minimumPrice,
|
|
303
|
-
minimumTotalSupply: 1,
|
|
304
|
-
maximumTotalSupply: 100,
|
|
305
|
-
maximumSplitPercent: 0,
|
|
306
|
-
allowedAddresses: allowedAddresses
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
vm.prank(hookOwner);
|
|
310
|
-
publisher.configurePostingCriteriaFor(allowedPosts);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function _singlePost(bytes32 uri, uint104 price, uint24 category) internal pure returns (CTPost[] memory posts) {
|
|
314
|
-
posts = new CTPost[](1);
|
|
315
|
-
posts[0] = CTPost({
|
|
316
|
-
encodedIPFSUri: uri,
|
|
317
|
-
totalSupply: 10,
|
|
318
|
-
price: price,
|
|
319
|
-
category: category,
|
|
320
|
-
splitPercent: 0,
|
|
321
|
-
splits: new JBSplit[](0)
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function _singletonArray(address account) internal pure returns (address[] memory addrs) {
|
|
326
|
-
addrs = new address[](1);
|
|
327
|
-
addrs[0] = account;
|
|
328
|
-
}
|
|
329
|
-
}
|