@croptop/core-v6 0.0.41 → 0.0.43
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/package.json +9 -9
- package/references/runtime.md +1 -1
- package/script/ConfigureFeeProject.s.sol +16 -13
- package/src/CTDeployer.sol +23 -11
- package/src/CTPublisher.sol +34 -35
- package/src/interfaces/ICTPublisher.sol +1 -3
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ Many Croptop bugs are really deployment-shape bugs or posting-policy bugs, not g
|
|
|
53
53
|
1. `test/CTPublisher.t.sol`
|
|
54
54
|
2. `test/CTDeployer.t.sol`
|
|
55
55
|
3. `test/ClaimCollectionOwnership.t.sol`
|
|
56
|
-
4. `test/
|
|
56
|
+
4. `test/regression/FeeFallbackBlackhole.t.sol`
|
|
57
57
|
5. `test/regression/DuplicateUriFeeEvasion.t.sol`
|
|
58
58
|
|
|
59
59
|
## Integration Traps
|
|
@@ -105,7 +105,7 @@ src/
|
|
|
105
105
|
interfaces/
|
|
106
106
|
structs/
|
|
107
107
|
test/
|
|
108
|
-
publisher, deployer, fork, attack,
|
|
108
|
+
publisher, deployer, fork, attack, review, metadata, and regression coverage
|
|
109
109
|
script/
|
|
110
110
|
Deploy.s.sol
|
|
111
111
|
ConfigureFeeProject.s.sol
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@croptop/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.43",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-v6'"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@bananapus/721-hook-v6": "0.0.
|
|
33
|
-
"@bananapus/core-v6": "0.0.
|
|
34
|
-
"@bananapus/ownable-v6": "0.0.24",
|
|
35
|
-
"@bananapus/permission-ids-v6": "0.0.
|
|
36
|
-
"@bananapus/router-terminal-v6": "0.0.
|
|
37
|
-
"@bananapus/suckers-v6": "0.0.
|
|
32
|
+
"@bananapus/721-hook-v6": "^0.0.47",
|
|
33
|
+
"@bananapus/core-v6": "^0.0.44",
|
|
34
|
+
"@bananapus/ownable-v6": "^0.0.24",
|
|
35
|
+
"@bananapus/permission-ids-v6": "^0.0.23",
|
|
36
|
+
"@bananapus/router-terminal-v6": "^0.0.37",
|
|
37
|
+
"@bananapus/suckers-v6": "^0.0.37",
|
|
38
38
|
"@openzeppelin/contracts": "5.6.1",
|
|
39
|
-
"@rev-net/core-v6": "0.0.
|
|
39
|
+
"@rev-net/core-v6": "^0.0.45"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@bananapus/address-registry-v6": "0.0.25",
|
|
42
|
+
"@bananapus/address-registry-v6": "^0.0.25",
|
|
43
43
|
"@sphinx-labs/plugins": "0.33.3"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/references/runtime.md
CHANGED
|
@@ -24,4 +24,4 @@
|
|
|
24
24
|
|
|
25
25
|
- [`test/CTPublisher.t.sol`](../test/CTPublisher.t.sol) and [`test/Test_MetadataGeneration.t.sol`](../test/Test_MetadataGeneration.t.sol) for content and metadata behavior.
|
|
26
26
|
- [`test/CTDeployer.t.sol`](../test/CTDeployer.t.sol) and [`test/Fork.t.sol`](../test/Fork.t.sol) for live deployment assumptions.
|
|
27
|
-
- [`test/CroptopAttacks.t.sol`](../test/CroptopAttacks.t.sol) and [`test/
|
|
27
|
+
- [`test/CroptopAttacks.t.sol`](../test/CroptopAttacks.t.sol) and [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) when the issue could be in publisher or deployer behavior rather than one isolated function.
|
|
@@ -355,19 +355,22 @@ contract ConfigureFeeProjectScript is Script, Sphinx {
|
|
|
355
355
|
function deploy() public sphinx {
|
|
356
356
|
FeeProjectConfig memory feeProjectConfig = getCroptopRevnetConfig();
|
|
357
357
|
|
|
358
|
-
//
|
|
359
|
-
core.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
358
|
+
// Only deploy if the project hasn't already been configured (restart-safe).
|
|
359
|
+
if (address(core.directory.controllerOf(FEE_PROJECT_ID)) == address(0)) {
|
|
360
|
+
// Approve the basic deployer to configure the project and transfer it.
|
|
361
|
+
core.projects.approve({to: address(revnet.basic_deployer), tokenId: FEE_PROJECT_ID});
|
|
362
|
+
|
|
363
|
+
// Deploy the NANA fee project.
|
|
364
|
+
revnet.basic_deployer
|
|
365
|
+
.deployFor({
|
|
366
|
+
revnetId: FEE_PROJECT_ID,
|
|
367
|
+
configuration: feeProjectConfig.configuration,
|
|
368
|
+
terminalConfigurations: feeProjectConfig.terminalConfigurations,
|
|
369
|
+
suckerDeploymentConfiguration: feeProjectConfig.suckerDeploymentConfiguration,
|
|
370
|
+
tiered721HookConfiguration: feeProjectConfig.hookConfiguration,
|
|
371
|
+
allowedPosts: feeProjectConfig.allowedPosts
|
|
372
|
+
});
|
|
373
|
+
}
|
|
371
374
|
}
|
|
372
375
|
|
|
373
376
|
function _isDeployed(
|
package/src/CTDeployer.sol
CHANGED
|
@@ -120,7 +120,8 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
120
120
|
|
|
121
121
|
/// @notice Claim ownership of the collection.
|
|
122
122
|
/// @dev Two-step ownership transfer process:
|
|
123
|
-
/// Step 1 (this function):
|
|
123
|
+
/// Step 1 (this function): Revokes the deployer-scoped permissions granted at launch, then transfers hook
|
|
124
|
+
/// ownership to the project via `transferOwnershipToProject()`.
|
|
124
125
|
/// After this call, `hook.owner()` resolves dynamically through `PROJECTS.ownerOf(projectId)`.
|
|
125
126
|
/// Step 2 (caller must do separately): The project owner grants CTPublisher the `ADJUST_721_TIERS` permission
|
|
126
127
|
/// for the project so that `mintFrom()` continues to work.
|
|
@@ -132,11 +133,29 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
132
133
|
// Get the project ID of the hook.
|
|
133
134
|
uint256 projectId = hook.PROJECT_ID();
|
|
134
135
|
|
|
136
|
+
// Keep a reference to the caller.
|
|
137
|
+
address caller = _msgSender();
|
|
138
|
+
|
|
135
139
|
// Make sure the caller is the owner of the project.
|
|
136
|
-
if (PROJECTS.ownerOf(projectId) !=
|
|
137
|
-
revert CTDeployer_NotOwnerOfProject({projectId: projectId, hook: address(hook), caller:
|
|
140
|
+
if (PROJECTS.ownerOf(projectId) != caller) {
|
|
141
|
+
revert CTDeployer_NotOwnerOfProject({projectId: projectId, hook: address(hook), caller: caller});
|
|
138
142
|
}
|
|
139
143
|
|
|
144
|
+
// Revoke the deployer-scoped permissions that were granted to the caller during deployment.
|
|
145
|
+
// These permissions (ADJUST_721_TIERS, SET_721_METADATA, MINT_721, SET_721_DISCOUNT_PERCENT) allowed the
|
|
146
|
+
// project owner to manage the hook while the deployer owned it. After transferring hook ownership to the
|
|
147
|
+
// project, these deployer-scoped grants are no longer needed and should be cleaned up to prevent stale
|
|
148
|
+
// permission leakage.
|
|
149
|
+
PERMISSIONS.setPermissionsFor({
|
|
150
|
+
account: address(this),
|
|
151
|
+
permissionsData: JBPermissionsData({
|
|
152
|
+
operator: caller,
|
|
153
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
154
|
+
projectId: uint64(projectId),
|
|
155
|
+
permissionIds: new uint8[](0)
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
|
|
140
159
|
// Transfer the hook's ownership to the project.
|
|
141
160
|
JBOwnable(address(hook)).transferOwnershipToProject(projectId);
|
|
142
161
|
}
|
|
@@ -171,7 +190,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
171
190
|
projectId = PROJECTS.createFor(address(this));
|
|
172
191
|
|
|
173
192
|
// Deploy a blank project.
|
|
174
|
-
// slither-disable-next-line reentrancy-benign
|
|
175
193
|
hook = DEPLOYER.deployHookFor({
|
|
176
194
|
projectId: projectId,
|
|
177
195
|
deployTiersHookConfig: JBDeploy721TiersHookConfig({
|
|
@@ -200,7 +218,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
200
218
|
rulesetConfigurations[0].metadata.useDataHookForCashOut = true;
|
|
201
219
|
|
|
202
220
|
// Launch the rulesets for the reserved project.
|
|
203
|
-
// slither-disable-next-line unused-return
|
|
204
221
|
controller.launchRulesetsFor({
|
|
205
222
|
projectId: projectId,
|
|
206
223
|
projectUri: projectConfig.projectUri,
|
|
@@ -214,7 +231,7 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
214
231
|
|
|
215
232
|
// Configure allowed posts.
|
|
216
233
|
if (projectConfig.allowedPosts.length > 0) {
|
|
217
|
-
_configurePostingCriteriaFor(address(hook), projectConfig.allowedPosts);
|
|
234
|
+
_configurePostingCriteriaFor({hook: address(hook), allowedPosts: projectConfig.allowedPosts});
|
|
218
235
|
}
|
|
219
236
|
|
|
220
237
|
// Deploy the suckers (if applicable).
|
|
@@ -226,7 +243,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
226
243
|
|
|
227
244
|
// Successful deployments are discoverable from the registry, and failures are reported without reverting
|
|
228
245
|
// the project launch.
|
|
229
|
-
// slither-disable-next-line unused-return
|
|
230
246
|
try SUCKER_REGISTRY.deploySuckersFor({
|
|
231
247
|
projectId: projectId,
|
|
232
248
|
salt: suckerSalt,
|
|
@@ -237,7 +253,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
237
253
|
// no-op
|
|
238
254
|
}
|
|
239
255
|
catch (bytes memory reason) {
|
|
240
|
-
// slither-disable-next-line reentrancy-events
|
|
241
256
|
emit CTDeployer_SuckerDeploymentFailed({projectId: projectId, salt: suckerSalt, reason: reason});
|
|
242
257
|
}
|
|
243
258
|
}
|
|
@@ -283,7 +298,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
283
298
|
|
|
284
299
|
// Deploy the suckers. The sucker registry performs its own permission check against this forwarding helper,
|
|
285
300
|
// so an unapproved CTDeployer fails at the downstream registry boundary without an extra preflight read here.
|
|
286
|
-
// slither-disable-next-line unused-return
|
|
287
301
|
suckers = SUCKER_REGISTRY.deploySuckersFor({
|
|
288
302
|
projectId: projectId,
|
|
289
303
|
salt: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
|
|
@@ -334,7 +348,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
334
348
|
hookSpecifications
|
|
335
349
|
);
|
|
336
350
|
}
|
|
337
|
-
// slither-disable-next-line unused-return
|
|
338
351
|
return hook.beforeCashOutRecordedWith(context);
|
|
339
352
|
}
|
|
340
353
|
|
|
@@ -358,7 +371,6 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
358
371
|
return (context.weight, hookSpecifications);
|
|
359
372
|
}
|
|
360
373
|
|
|
361
|
-
// slither-disable-next-line unused-return
|
|
362
374
|
return hook.beforePayRecordedWith(context);
|
|
363
375
|
}
|
|
364
376
|
|
package/src/CTPublisher.sol
CHANGED
|
@@ -32,19 +32,20 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
32
32
|
//*********************************************************************//
|
|
33
33
|
|
|
34
34
|
error CTPublisher_DuplicatePost(bytes32 encodedIPFSUri);
|
|
35
|
-
error CTPublisher_EmptyEncodedIPFSUri();
|
|
35
|
+
error CTPublisher_EmptyEncodedIPFSUri(uint256 postIndex);
|
|
36
36
|
error CTPublisher_InsufficientEthSent(uint256 expected, uint256 sent);
|
|
37
37
|
error CTPublisher_MaxTotalSupplyLessThanMin(uint256 min, uint256 max);
|
|
38
38
|
error CTPublisher_NotInAllowList(address addr, address[] allowedAddresses);
|
|
39
39
|
error CTPublisher_PriceTooSmall(uint256 price, uint256 minimumPrice);
|
|
40
|
-
error CTPublisher_DuplicatePayMetadata();
|
|
40
|
+
error CTPublisher_DuplicatePayMetadata(bytes4 payMetadataId);
|
|
41
41
|
error CTPublisher_FeePaymentFailed(uint256 feeAmount);
|
|
42
42
|
error CTPublisher_SplitPercentExceedsMaximum(uint256 splitPercent, uint256 maximumSplitPercent);
|
|
43
43
|
error CTPublisher_TotalSupplyTooBig(uint256 totalSupply, uint256 maximumTotalSupply);
|
|
44
44
|
error CTPublisher_TotalSupplyTooSmall(uint256 totalSupply, uint256 minimumTotalSupply);
|
|
45
|
-
error CTPublisher_NoPosts();
|
|
46
|
-
error
|
|
47
|
-
error
|
|
45
|
+
error CTPublisher_NoPosts(address caller);
|
|
46
|
+
error CTPublisher_InvalidFeeBeneficiary();
|
|
47
|
+
error CTPublisher_UnauthorizedToPostInCategory(address hook, uint24 category);
|
|
48
|
+
error CTPublisher_ZeroTotalSupply(address hook, uint24 category);
|
|
48
49
|
|
|
49
50
|
//*********************************************************************//
|
|
50
51
|
// ------------------------- public constants ------------------------ //
|
|
@@ -133,7 +134,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
133
134
|
emit ConfigurePostingCriteria({hook: allowedPost.hook, allowedPost: allowedPost, caller: _msgSender()});
|
|
134
135
|
|
|
135
136
|
// Enforce permissions.
|
|
136
|
-
// slither-disable-next-line reentrancy-events,calls-loop
|
|
137
137
|
_requirePermissionFrom({
|
|
138
138
|
account: JBOwnable(allowedPost.hook).owner(),
|
|
139
139
|
projectId: IJB721TiersHook(allowedPost.hook).PROJECT_ID(),
|
|
@@ -142,14 +142,14 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
142
142
|
|
|
143
143
|
// Make sure there is a minimum supply.
|
|
144
144
|
if (allowedPost.minimumTotalSupply == 0) {
|
|
145
|
-
revert CTPublisher_ZeroTotalSupply();
|
|
145
|
+
revert CTPublisher_ZeroTotalSupply({hook: allowedPost.hook, category: allowedPost.category});
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// Make sure the minimum supply does not surpass the maximum supply.
|
|
149
149
|
if (allowedPost.minimumTotalSupply > allowedPost.maximumTotalSupply) {
|
|
150
|
-
revert CTPublisher_MaxTotalSupplyLessThanMin(
|
|
151
|
-
allowedPost.minimumTotalSupply, allowedPost.maximumTotalSupply
|
|
152
|
-
);
|
|
150
|
+
revert CTPublisher_MaxTotalSupplyLessThanMin({
|
|
151
|
+
min: allowedPost.minimumTotalSupply, max: allowedPost.maximumTotalSupply
|
|
152
|
+
});
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
uint256 packed;
|
|
@@ -190,21 +190,22 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
190
190
|
/// @param additionalPayMetadata Metadata bytes that should be included in the pay function's metadata. This
|
|
191
191
|
/// prepends the
|
|
192
192
|
/// payload needed for NFT creation.
|
|
193
|
-
/// @param feeMetadata The metadata to send alongside the fee payment.
|
|
194
193
|
function mintFrom(
|
|
195
194
|
IJB721TiersHook hook,
|
|
196
195
|
CTPost[] calldata posts,
|
|
197
196
|
address nftBeneficiary,
|
|
198
197
|
address feeBeneficiary,
|
|
199
|
-
bytes calldata additionalPayMetadata
|
|
200
|
-
bytes calldata feeMetadata
|
|
198
|
+
bytes calldata additionalPayMetadata
|
|
201
199
|
)
|
|
202
200
|
external
|
|
203
201
|
payable
|
|
204
202
|
override
|
|
205
203
|
{
|
|
206
204
|
// Reject empty posts to prevent fee-free metadata shadowing.
|
|
207
|
-
if (posts.length == 0) revert CTPublisher_NoPosts();
|
|
205
|
+
if (posts.length == 0) revert CTPublisher_NoPosts(_msgSender());
|
|
206
|
+
|
|
207
|
+
// Reject address(0) as fee beneficiary to prevent burning fee project tokens.
|
|
208
|
+
if (feeBeneficiary == address(0)) revert CTPublisher_InvalidFeeBeneficiary();
|
|
208
209
|
|
|
209
210
|
// Keep a reference to the amount being paid, which is msg.value minus the fee.
|
|
210
211
|
uint256 payValue = msg.value;
|
|
@@ -218,7 +219,7 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
218
219
|
{
|
|
219
220
|
// Setup the posts.
|
|
220
221
|
(JB721TierConfig[] memory tiersToAdd, uint256[] memory tierIdsToMint, uint256 totalPrice) =
|
|
221
|
-
_setupPosts(hook, posts);
|
|
222
|
+
_setupPosts({hook: hook, posts: posts});
|
|
222
223
|
|
|
223
224
|
if (projectId != FEE_PROJECT_ID) {
|
|
224
225
|
// Keep a reference to the fee that will be paid.
|
|
@@ -229,7 +230,7 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
229
230
|
|
|
230
231
|
// Make sure enough ETH was sent to cover the fee.
|
|
231
232
|
if (payValue < fee) {
|
|
232
|
-
revert CTPublisher_InsufficientEthSent(totalPrice + fee, msg.value);
|
|
233
|
+
revert CTPublisher_InsufficientEthSent({expected: totalPrice + fee, sent: msg.value});
|
|
233
234
|
}
|
|
234
235
|
|
|
235
236
|
payValue -= fee;
|
|
@@ -237,11 +238,10 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
237
238
|
|
|
238
239
|
// Make sure the amount sent to this function is at least the specified price of the tier plus the fee.
|
|
239
240
|
if (totalPrice > payValue) {
|
|
240
|
-
revert CTPublisher_InsufficientEthSent(totalPrice, msg.value);
|
|
241
|
+
revert CTPublisher_InsufficientEthSent({expected: totalPrice, sent: msg.value});
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
// Add the new tiers.
|
|
244
|
-
// slither-disable-next-line reentrancy-events
|
|
245
245
|
hook.adjustTiers({tiersToAdd: tiersToAdd, tierIdsToRemove: new uint256[](0)});
|
|
246
246
|
|
|
247
247
|
// Keep a reference to the metadata ID target.
|
|
@@ -251,9 +251,8 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
251
251
|
// tier selection, allowing the caller to mint arbitrary tiers.
|
|
252
252
|
{
|
|
253
253
|
bytes4 payId = JBMetadataResolver.getId({purpose: "pay", target: metadataIdTarget});
|
|
254
|
-
// slither-disable-next-line unused-return
|
|
255
254
|
(bool exists,) = JBMetadataResolver.getDataFor({id: payId, metadata: additionalPayMetadata});
|
|
256
|
-
if (exists) revert CTPublisher_DuplicatePayMetadata();
|
|
255
|
+
if (exists) revert CTPublisher_DuplicatePayMetadata(payId);
|
|
257
256
|
}
|
|
258
257
|
|
|
259
258
|
// Create the metadata for the payment to specify the tier IDs that should be minted. We create manually the
|
|
@@ -290,7 +289,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
290
289
|
DIRECTORY.primaryTerminalOf({projectId: projectId, token: JBConstants.NATIVE_TOKEN});
|
|
291
290
|
|
|
292
291
|
// Make the payment.
|
|
293
|
-
// slither-disable-next-line unused-return
|
|
294
292
|
projectTerminal.pay{value: payValue}({
|
|
295
293
|
projectId: projectId,
|
|
296
294
|
token: JBConstants.NATIVE_TOKEN,
|
|
@@ -314,7 +312,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
314
312
|
|
|
315
313
|
// Make the fee payment. If the fee sink is unavailable, refund the fee to the caller
|
|
316
314
|
// rather than trapping or silently redirecting protocol funds.
|
|
317
|
-
// slither-disable-next-line unused-return
|
|
318
315
|
try feeTerminal.pay{value: payValue}({
|
|
319
316
|
projectId: FEE_PROJECT_ID,
|
|
320
317
|
amount: payValue,
|
|
@@ -322,10 +319,9 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
322
319
|
beneficiary: feeBeneficiary,
|
|
323
320
|
minReturnedTokens: 0,
|
|
324
321
|
memo: "",
|
|
325
|
-
metadata:
|
|
322
|
+
metadata: ""
|
|
326
323
|
}) {}
|
|
327
324
|
catch {
|
|
328
|
-
// slither-disable-next-line low-level-calls
|
|
329
325
|
(bool success,) = _msgSender().call{value: payValue}("");
|
|
330
326
|
if (!success) revert CTPublisher_FeePaymentFailed(payValue);
|
|
331
327
|
}
|
|
@@ -365,7 +361,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
365
361
|
|
|
366
362
|
// If there's a tier ID stored, resolve it.
|
|
367
363
|
if (tierId != 0) {
|
|
368
|
-
// slither-disable-next-line calls-loop
|
|
369
364
|
tiers[i] = IJB721TiersHook(hook).STORE().tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
370
365
|
}
|
|
371
366
|
|
|
@@ -465,13 +460,13 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
465
460
|
// Make sure the post includes an encodedIPFSUri.
|
|
466
461
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
467
462
|
if (post.encodedIPFSUri == bytes32("")) {
|
|
468
|
-
revert CTPublisher_EmptyEncodedIPFSUri();
|
|
463
|
+
revert CTPublisher_EmptyEncodedIPFSUri({postIndex: i});
|
|
469
464
|
}
|
|
470
465
|
|
|
471
466
|
// Check for duplicate encodedIPFSUri within the same batch to prevent fee evasion.
|
|
472
467
|
for (uint256 j; j < i;) {
|
|
473
468
|
if (posts[j].encodedIPFSUri == post.encodedIPFSUri) {
|
|
474
|
-
revert CTPublisher_DuplicatePost(post.encodedIPFSUri);
|
|
469
|
+
revert CTPublisher_DuplicatePost({encodedIPFSUri: post.encodedIPFSUri});
|
|
475
470
|
}
|
|
476
471
|
unchecked {
|
|
477
472
|
++j;
|
|
@@ -488,10 +483,8 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
488
483
|
// The cache can become stale if the tier was removed (via adjustTiers) or
|
|
489
484
|
// its URI was changed (via setMetadata). In either case, clear the stale
|
|
490
485
|
// mapping and fall through to create a new tier.
|
|
491
|
-
// slither-disable-next-line calls-loop
|
|
492
486
|
JB721Tier memory cachedTier =
|
|
493
487
|
store.tierOf({hook: address(hook), id: tierId, includeResolvedUri: false});
|
|
494
|
-
// slither-disable-next-line calls-loop
|
|
495
488
|
if (store.isTierRemoved(address(hook), tierId) || cachedTier.encodedIPFSUri != post.encodedIPFSUri)
|
|
496
489
|
{
|
|
497
490
|
delete tierIdForEncodedIPFSUriOf[address(hook)][post.encodedIPFSUri];
|
|
@@ -520,34 +513,40 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
520
513
|
|
|
521
514
|
// Make sure the category being posted to allows publishing.
|
|
522
515
|
if (minimumTotalSupply == 0) {
|
|
523
|
-
revert CTPublisher_UnauthorizedToPostInCategory();
|
|
516
|
+
revert CTPublisher_UnauthorizedToPostInCategory({hook: address(hook), category: post.category});
|
|
524
517
|
}
|
|
525
518
|
|
|
526
519
|
// Make sure the price being paid for the post is at least the allowed minimum price.
|
|
527
520
|
if (post.price < minimumPrice) {
|
|
528
|
-
revert CTPublisher_PriceTooSmall(post.price, minimumPrice);
|
|
521
|
+
revert CTPublisher_PriceTooSmall({price: post.price, minimumPrice: minimumPrice});
|
|
529
522
|
}
|
|
530
523
|
|
|
531
524
|
// Make sure the total supply being made available for the post is at least the allowed minimum
|
|
532
525
|
// total supply.
|
|
533
526
|
if (post.totalSupply < minimumTotalSupply) {
|
|
534
|
-
revert CTPublisher_TotalSupplyTooSmall(
|
|
527
|
+
revert CTPublisher_TotalSupplyTooSmall({
|
|
528
|
+
totalSupply: post.totalSupply, minimumTotalSupply: minimumTotalSupply
|
|
529
|
+
});
|
|
535
530
|
}
|
|
536
531
|
|
|
537
532
|
// Make sure the total supply being made available for the post is at most the allowed maximum total
|
|
538
533
|
// supply.
|
|
539
534
|
if (post.totalSupply > maximumTotalSupply) {
|
|
540
|
-
revert CTPublisher_TotalSupplyTooBig(
|
|
535
|
+
revert CTPublisher_TotalSupplyTooBig({
|
|
536
|
+
totalSupply: post.totalSupply, maximumTotalSupply: maximumTotalSupply
|
|
537
|
+
});
|
|
541
538
|
}
|
|
542
539
|
|
|
543
540
|
// Make sure the split percent is within the allowed maximum.
|
|
544
541
|
if (post.splitPercent > maximumSplitPercent) {
|
|
545
|
-
revert CTPublisher_SplitPercentExceedsMaximum(
|
|
542
|
+
revert CTPublisher_SplitPercentExceedsMaximum({
|
|
543
|
+
splitPercent: post.splitPercent, maximumSplitPercent: maximumSplitPercent
|
|
544
|
+
});
|
|
546
545
|
}
|
|
547
546
|
|
|
548
547
|
// Make sure the address is allowed to post.
|
|
549
548
|
if (addresses.length != 0 && !_isAllowed({addrs: _msgSender(), addresses: addresses})) {
|
|
550
|
-
revert CTPublisher_NotInAllowList(_msgSender(), addresses);
|
|
549
|
+
revert CTPublisher_NotInAllowList({addr: _msgSender(), allowedAddresses: addresses});
|
|
551
550
|
}
|
|
552
551
|
}
|
|
553
552
|
|
|
@@ -93,14 +93,12 @@ interface ICTPublisher {
|
|
|
93
93
|
/// @param nftBeneficiary The beneficiary of the NFT mints.
|
|
94
94
|
/// @param feeBeneficiary The beneficiary of the fee project's tokens.
|
|
95
95
|
/// @param additionalPayMetadata Extra metadata bytes to include in the payment.
|
|
96
|
-
/// @param feeMetadata Metadata to send alongside the fee payment.
|
|
97
96
|
function mintFrom(
|
|
98
97
|
IJB721TiersHook hook,
|
|
99
98
|
CTPost[] calldata posts,
|
|
100
99
|
address nftBeneficiary,
|
|
101
100
|
address feeBeneficiary,
|
|
102
|
-
bytes calldata additionalPayMetadata
|
|
103
|
-
bytes calldata feeMetadata
|
|
101
|
+
bytes calldata additionalPayMetadata
|
|
104
102
|
)
|
|
105
103
|
external
|
|
106
104
|
payable;
|