@croptop/core-v6 0.0.1

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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/SKILLS.md +88 -0
  4. package/deployments/croptop-core-v5/arbitrum/CTDeployer.json +1896 -0
  5. package/deployments/croptop-core-v5/arbitrum/CTProjectOwner.json +186 -0
  6. package/deployments/croptop-core-v5/arbitrum/CTPublisher.json +738 -0
  7. package/deployments/croptop-core-v5/arbitrum_sepolia/CTDeployer.json +1883 -0
  8. package/deployments/croptop-core-v5/arbitrum_sepolia/CTProjectOwner.json +186 -0
  9. package/deployments/croptop-core-v5/arbitrum_sepolia/CTPublisher.json +738 -0
  10. package/deployments/croptop-core-v5/base/CTDeployer.json +1908 -0
  11. package/deployments/croptop-core-v5/base/CTProjectOwner.json +190 -0
  12. package/deployments/croptop-core-v5/base/CTPublisher.json +741 -0
  13. package/deployments/croptop-core-v5/base_sepolia/CTDeployer.json +1894 -0
  14. package/deployments/croptop-core-v5/base_sepolia/CTProjectOwner.json +190 -0
  15. package/deployments/croptop-core-v5/base_sepolia/CTPublisher.json +741 -0
  16. package/deployments/croptop-core-v5/ethereum/CTDeployer.json +1894 -0
  17. package/deployments/croptop-core-v5/ethereum/CTProjectOwner.json +190 -0
  18. package/deployments/croptop-core-v5/ethereum/CTPublisher.json +741 -0
  19. package/deployments/croptop-core-v5/optimism/CTDeployer.json +1894 -0
  20. package/deployments/croptop-core-v5/optimism/CTProjectOwner.json +190 -0
  21. package/deployments/croptop-core-v5/optimism/CTPublisher.json +741 -0
  22. package/deployments/croptop-core-v5/optimism_sepolia/CTDeployer.json +1894 -0
  23. package/deployments/croptop-core-v5/optimism_sepolia/CTProjectOwner.json +190 -0
  24. package/deployments/croptop-core-v5/optimism_sepolia/CTPublisher.json +741 -0
  25. package/deployments/croptop-core-v5/sepolia/CTDeployer.json +1894 -0
  26. package/deployments/croptop-core-v5/sepolia/CTProjectOwner.json +190 -0
  27. package/deployments/croptop-core-v5/sepolia/CTPublisher.json +741 -0
  28. package/foundry.toml +25 -0
  29. package/package.json +31 -0
  30. package/remappings.txt +2 -0
  31. package/script/ConfigureFeeProject.s.sol +386 -0
  32. package/script/Deploy.s.sol +138 -0
  33. package/script/helpers/CroptopDeploymentLib.sol +75 -0
  34. package/slither-ci.config.json +10 -0
  35. package/sphinx.lock +507 -0
  36. package/src/CTDeployer.sol +425 -0
  37. package/src/CTProjectOwner.sol +78 -0
  38. package/src/CTPublisher.sol +540 -0
  39. package/src/interfaces/ICTDeployer.sol +56 -0
  40. package/src/interfaces/ICTProjectOwner.sol +24 -0
  41. package/src/interfaces/ICTPublisher.sol +91 -0
  42. package/src/structs/CTAllowedPost.sol +22 -0
  43. package/src/structs/CTDeployerAllowedPost.sol +20 -0
  44. package/src/structs/CTPost.sol +22 -0
  45. package/src/structs/CTProjectConfig.sol +22 -0
  46. package/src/structs/CTSuckerDeploymentConfig.sol +11 -0
  47. package/test/CTPublisher.t.sol +672 -0
  48. package/test/CroptopAttacks.t.sol +439 -0
  49. package/test/Fork.t.sol +114 -0
  50. package/test/Test_MetadataGeneration.t.sol +70 -0
@@ -0,0 +1,425 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.23;
3
+
4
+ import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
5
+ import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
6
+ import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
7
+ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
8
+ import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
9
+ import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
10
+ import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
11
+ import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
12
+ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
13
+ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
14
+ import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
15
+ import {JBOwnable} from "@bananapus/ownable-v6/src/JBOwnable.sol";
16
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
17
+ import {Context} from "@openzeppelin/contracts/utils/Context.sol";
18
+
19
+ import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
20
+ import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
21
+ import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
22
+ import {JB721InitTiersConfig} from "@bananapus/721-hook-v6/src/structs/JB721InitTiersConfig.sol";
23
+ import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
24
+ import {JB721TiersHookFlags} from "@bananapus/721-hook-v6/src/structs/JB721TiersHookFlags.sol";
25
+ import {JBDeploy721TiersHookConfig} from "@bananapus/721-hook-v6/src/structs/JBDeploy721TiersHookConfig.sol";
26
+ import {JBLaunchProjectConfig} from "@bananapus/721-hook-v6/src/structs/JBLaunchProjectConfig.sol";
27
+ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
28
+ import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
29
+ import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
30
+ import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
31
+ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
32
+ import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
33
+ import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
34
+
35
+ import {ICTDeployer} from "./interfaces/ICTDeployer.sol";
36
+ import {ICTPublisher} from "./interfaces/ICTPublisher.sol";
37
+ import {CTAllowedPost} from "./structs/CTAllowedPost.sol";
38
+ import {CTSuckerDeploymentConfig} from "./structs/CTSuckerDeploymentConfig.sol";
39
+ import {CTDeployerAllowedPost} from "./structs/CTDeployerAllowedPost.sol";
40
+ import {CTProjectConfig} from "./structs/CTProjectConfig.sol";
41
+
42
+ /// @notice A contract that facilitates deploying a simple Juicebox project to receive posts from Croptop templates.
43
+ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC721Receiver, ICTDeployer {
44
+ //*********************************************************************//
45
+ // --------------------------- custom errors ------------------------- //
46
+ //*********************************************************************//
47
+
48
+ error CTDeployer_NotOwnerOfProject(uint256 projectId, address hook, address caller);
49
+
50
+ //*********************************************************************//
51
+ // ---------------- public immutable stored properties --------------- //
52
+ //*********************************************************************//
53
+
54
+ /// @notice Mints ERC-721s that represent Juicebox project ownership and transfers.
55
+ IJBProjects public immutable override PROJECTS;
56
+
57
+ /// @notice The deployer to launch Croptop recorded collections from.
58
+ IJB721TiersHookDeployer public immutable override DEPLOYER;
59
+
60
+ /// @notice The Croptop publisher.
61
+ ICTPublisher public immutable override PUBLISHER;
62
+
63
+ /// @notice Deploys and tracks suckers for projects.
64
+ IJBSuckerRegistry public immutable SUCKER_REGISTRY;
65
+
66
+ //*********************************************************************//
67
+ // --------------------- public stored properties -------------------- //
68
+ //*********************************************************************//
69
+
70
+ /// @notice Each project's data hook provided on deployment.
71
+ /// @custom:param projectId The ID of the project to get the data hook for.
72
+ /// @custom:param rulesetId The ID of the ruleset to get the data hook for.
73
+ mapping(uint256 projectId => IJBRulesetDataHook) public dataHookOf;
74
+
75
+ //*********************************************************************//
76
+ // -------------------------- constructor ---------------------------- //
77
+ //*********************************************************************//
78
+
79
+ /// @param permissions The permissions contract.
80
+ /// @param projects The projects contract.
81
+ /// @param deployer The deployer to launch Croptop projects from.
82
+ /// @param publisher The croptop publisher.
83
+ /// @param suckerRegistry The sucker registry.
84
+ /// @param trustedForwarder The trusted forwarder.
85
+ constructor(
86
+ IJBPermissions permissions,
87
+ IJBProjects projects,
88
+ IJB721TiersHookDeployer deployer,
89
+ ICTPublisher publisher,
90
+ IJBSuckerRegistry suckerRegistry,
91
+ address trustedForwarder
92
+ )
93
+ ERC2771Context(trustedForwarder)
94
+ JBPermissioned(permissions)
95
+ {
96
+ PROJECTS = projects;
97
+ DEPLOYER = deployer;
98
+ PUBLISHER = publisher;
99
+ SUCKER_REGISTRY = suckerRegistry;
100
+
101
+ // Give the sucker registry permission to map tokens for all revnets.
102
+ uint8[] memory permissionIds = new uint8[](1);
103
+ permissionIds[0] = JBPermissionIds.MAP_SUCKER_TOKEN;
104
+
105
+ // Give the operator the permission.
106
+ // Set up the permission data.
107
+ JBPermissionsData memory permissionData =
108
+ JBPermissionsData({operator: address(SUCKER_REGISTRY), projectId: 0, permissionIds: permissionIds});
109
+
110
+ // Set the permissions.
111
+ PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
112
+
113
+ // Set permission for the CTPublisher to adjust the tier.
114
+ permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
115
+
116
+ // Set permission for the CTPublisher to mint the NFT.
117
+ permissionData = JBPermissionsData({operator: address(PUBLISHER), projectId: 0, permissionIds: permissionIds});
118
+
119
+ // Set permission for the CTPublisher to adjust the tier.
120
+ PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
121
+ }
122
+
123
+ //*********************************************************************//
124
+ // ------------------------- external views -------------------------- //
125
+ //*********************************************************************//
126
+
127
+ /// @notice Forward the call to the original data hook.
128
+ /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a payment.
129
+ /// @param context Standard Juicebox payment context. See `JBBeforePayRecordedContext`.
130
+ /// @return weight The weight which project tokens are minted relative to. This can be used to customize how many
131
+ /// tokens get minted by a payment.
132
+ /// @return hookSpecifications Amounts (out of what's being paid in) to be sent to pay hooks instead of being paid
133
+ /// into the project. Useful for automatically routing funds from a treasury as payments come in.
134
+ function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
135
+ external
136
+ view
137
+ override
138
+ returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
139
+ {
140
+ // Forward the call to the data hook.
141
+ // slither-disable-next-line unused-return
142
+ return dataHookOf[context.projectId].beforePayRecordedWith(context);
143
+ }
144
+
145
+ /// @notice Allow cash outs from suckers without a tax.
146
+ /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a cash out.
147
+ /// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
148
+ /// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
149
+ /// out.
150
+ /// @return cashOutCount The number of project tokens that are cashed out.
151
+ /// @return totalSupply The total project token supply.
152
+ /// @return hookSpecifications The amount of funds and the data to send to cash out hooks (this contract).
153
+ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
154
+ external
155
+ view
156
+ override
157
+ returns (
158
+ uint256 cashOutTaxRate,
159
+ uint256 cashOutCount,
160
+ uint256 totalSupply,
161
+ JBCashOutHookSpecification[] memory hookSpecifications
162
+ )
163
+ {
164
+ // If the cash out is from a sucker, return the full cash out amount without taxes or fees.
165
+ if (SUCKER_REGISTRY.isSuckerOf({projectId: context.projectId, addr: context.holder})) {
166
+ return (0, context.cashOutCount, context.totalSupply, hookSpecifications);
167
+ }
168
+
169
+ // If the ruleset has a data hook, forward the call to the datahook.
170
+ // slither-disable-next-line unused-return
171
+ return dataHookOf[context.projectId].beforeCashOutRecordedWith(context);
172
+ }
173
+
174
+ /// @notice A flag indicating whether an address has permission to mint a project's tokens on-demand.
175
+ /// @dev A project's data hook can allow any address to mint its tokens.
176
+ /// @param projectId The ID of the project whose token can be minted.
177
+ /// @param addr The address to check the token minting permission of.
178
+ /// @return flag A flag indicating whether the address has permission to mint the project's tokens on-demand.
179
+ function hasMintPermissionFor(uint256 projectId, JBRuleset memory, address addr) external view returns (bool flag) {
180
+ // If the address is a sucker for this project.
181
+ return SUCKER_REGISTRY.isSuckerOf({projectId: projectId, addr: addr});
182
+ }
183
+
184
+ /// @dev Make sure only mints can be received.
185
+ function onERC721Received(
186
+ address operator,
187
+ address from,
188
+ uint256 tokenId,
189
+ bytes calldata data
190
+ )
191
+ external
192
+ view
193
+ returns (bytes4)
194
+ {
195
+ data;
196
+ tokenId;
197
+ operator;
198
+
199
+ // Make sure the 721 received is the JBProjects contract.
200
+ if (msg.sender != address(PROJECTS)) revert();
201
+ // Make sure the 721 is being received as a mint.
202
+ if (from != address(0)) revert();
203
+ return IERC721Receiver.onERC721Received.selector;
204
+ }
205
+
206
+ //*********************************************************************//
207
+ // -------------------------- public views --------------------------- //
208
+ //*********************************************************************//
209
+
210
+ /// @notice Indicates if this contract adheres to the specified interface.
211
+ /// @dev See `IERC165.supportsInterface`.
212
+ /// @return A flag indicating if the provided interface ID is supported.
213
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
214
+ return interfaceId == type(ICTDeployer).interfaceId || interfaceId == type(IJBRulesetDataHook).interfaceId
215
+ || interfaceId == type(IERC721Receiver).interfaceId;
216
+ }
217
+
218
+ //*********************************************************************//
219
+ // ---------------------- external transactions ---------------------- //
220
+ //*********************************************************************//
221
+
222
+ /// @notice Deploy a simple project meant to receive posts from Croptop templates.
223
+ /// @param owner The address that'll own the project.
224
+ /// @param projectConfig The configuration for the project.
225
+ /// @param suckerDeploymentConfiguration The configuration for the suckers to deploy.
226
+ /// @param controller The controller that will own the project.
227
+ /// @return projectId The ID of the newly created project.
228
+ /// @return hook The hook that was created.
229
+ function deployProjectFor(
230
+ address owner,
231
+ CTProjectConfig calldata projectConfig,
232
+ CTSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
233
+ IJBController controller
234
+ )
235
+ external
236
+ override
237
+ returns (uint256 projectId, IJB721TiersHook hook)
238
+ {
239
+ if (controller.PROJECTS() != PROJECTS) revert();
240
+
241
+ JBRulesetConfig[] memory rulesetConfigurations = new JBRulesetConfig[](1);
242
+ rulesetConfigurations[0].weight = 1_000_000 * (10 ** 18);
243
+ rulesetConfigurations[0].metadata.baseCurrency = JBCurrencyIds.ETH;
244
+
245
+ // Get the next project ID.
246
+ projectId = PROJECTS.count() + 1;
247
+
248
+ // Deploy a blank project.
249
+ // slither-disable-next-line reentrancy-benign
250
+ hook = DEPLOYER.deployHookFor({
251
+ projectId: projectId,
252
+ deployTiersHookConfig: JBDeploy721TiersHookConfig({
253
+ name: projectConfig.name,
254
+ symbol: projectConfig.symbol,
255
+ baseUri: "ipfs://",
256
+ tokenUriResolver: IJB721TokenUriResolver(address(0)),
257
+ contractUri: projectConfig.contractUri,
258
+ tiersConfig: JB721InitTiersConfig({
259
+ tiers: new JB721TierConfig[](0),
260
+ currency: JBCurrencyIds.ETH,
261
+ decimals: 18,
262
+ prices: controller.PRICES()
263
+ }),
264
+ reserveBeneficiary: address(0),
265
+ flags: JB721TiersHookFlags({
266
+ noNewTiersWithReserves: false,
267
+ noNewTiersWithVotes: false,
268
+ noNewTiersWithOwnerMinting: false,
269
+ preventOverspending: false
270
+ })
271
+ }),
272
+ salt: keccak256(abi.encode(projectConfig.salt, _msgSender()))
273
+ });
274
+
275
+ rulesetConfigurations[0].metadata.cashOutTaxRate = JBConstants.MAX_CASH_OUT_TAX_RATE;
276
+ rulesetConfigurations[0].metadata.dataHook = address(this);
277
+ rulesetConfigurations[0].metadata.useDataHookForPay = true;
278
+
279
+ // Launch the project, and sanity check the project ID.
280
+ assert(
281
+ projectId
282
+ == controller.launchProjectFor({
283
+ owner: address(this),
284
+ projectUri: projectConfig.projectUri,
285
+ rulesetConfigurations: rulesetConfigurations,
286
+ terminalConfigurations: projectConfig.terminalConfigurations,
287
+ memo: "Deployed from Croptop"
288
+ })
289
+ );
290
+
291
+ // Set the data hook for the project.
292
+ dataHookOf[projectId] = IJBRulesetDataHook(hook);
293
+
294
+ // Configure allowed posts.
295
+ if (projectConfig.allowedPosts.length > 0) {
296
+ _configurePostingCriteriaFor(address(hook), projectConfig.allowedPosts);
297
+ }
298
+
299
+ // Deploy the suckers (if applicable).
300
+ if (suckerDeploymentConfiguration.salt != bytes32(0)) {
301
+ // slither-disable-next-line unused-return
302
+ SUCKER_REGISTRY.deploySuckersFor({
303
+ projectId: projectId,
304
+ salt: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
305
+ configurations: suckerDeploymentConfiguration.deployerConfigurations
306
+ });
307
+ }
308
+
309
+ //transfer to _owner.
310
+ PROJECTS.transferFrom(address(this), owner, projectId);
311
+
312
+ // Set permission for the project's owner to do all the NFT things.
313
+ uint8[] memory permissionIds = new uint8[](4);
314
+ permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
315
+ permissionIds[1] = JBPermissionIds.SET_721_METADATA;
316
+ permissionIds[2] = JBPermissionIds.MINT_721;
317
+ permissionIds[3] = JBPermissionIds.SET_721_DISCOUNT_PERCENT;
318
+
319
+ PERMISSIONS.setPermissionsFor({
320
+ account: address(this),
321
+ permissionsData: JBPermissionsData({
322
+ operator: address(owner), projectId: uint64(projectId), permissionIds: permissionIds
323
+ })
324
+ });
325
+ }
326
+
327
+ /// @notice Claim ownership of the collection.
328
+ /// @param hook The hook to claim ownership of.
329
+ function claimCollectionOwnershipOf(IJB721TiersHook hook) external override {
330
+ // Get the project ID of the hook.
331
+ uint256 projectId = hook.PROJECT_ID();
332
+
333
+ // Make sure the caller is the owner of the project.
334
+ if (PROJECTS.ownerOf(projectId) != _msgSender()) {
335
+ revert CTDeployer_NotOwnerOfProject(projectId, address(hook), _msgSender());
336
+ }
337
+
338
+ // Transfer the hook's ownership to the project.
339
+ JBOwnable(address(hook)).transferOwnershipToProject(projectId);
340
+ }
341
+
342
+ /// @notice Deploy new suckers for an existing project.
343
+ /// @dev Only the juicebox's owner can deploy new suckers.
344
+ /// @param projectId The ID of the project to deploy suckers for.
345
+ /// @param suckerDeploymentConfiguration The suckers to set up for the project.
346
+ function deploySuckersFor(
347
+ uint256 projectId,
348
+ CTSuckerDeploymentConfig calldata suckerDeploymentConfiguration
349
+ )
350
+ external
351
+ returns (address[] memory suckers)
352
+ {
353
+ // Enforce permissions.
354
+ _requirePermissionFrom({
355
+ account: PROJECTS.ownerOf(projectId), projectId: projectId, permissionId: JBPermissionIds.DEPLOY_SUCKERS
356
+ });
357
+
358
+ // Deploy the suckers.
359
+ // slither-disable-next-line unused-return
360
+ suckers = SUCKER_REGISTRY.deploySuckersFor({
361
+ projectId: projectId,
362
+ salt: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
363
+ configurations: suckerDeploymentConfiguration.deployerConfigurations
364
+ });
365
+ }
366
+
367
+ //*********************************************************************//
368
+ // --------------------- internal transactions ----------------------- //
369
+ //*********************************************************************//
370
+
371
+ /// @notice Configure croptop posting.
372
+ /// @param hook The hook that will be posted to.
373
+ /// @param allowedPosts The type of posts that should be allowed.
374
+ function _configurePostingCriteriaFor(address hook, CTDeployerAllowedPost[] memory allowedPosts) internal {
375
+ // Keep a reference to the number of allowed posts.
376
+ uint256 numberOfAllowedPosts = allowedPosts.length;
377
+
378
+ // Keep a reference to the formatted allowed posts.
379
+ CTAllowedPost[] memory formattedAllowedPosts = new CTAllowedPost[](numberOfAllowedPosts);
380
+
381
+ // Keep a reference to the post being iterated on.
382
+ CTDeployerAllowedPost memory post;
383
+
384
+ // Iterate through each post to add it to the formatted list.
385
+ for (uint256 i; i < numberOfAllowedPosts; i++) {
386
+ // Set the post being iterated on.
387
+ post = allowedPosts[i];
388
+
389
+ // Set the formatted post.
390
+ formattedAllowedPosts[i] = CTAllowedPost({
391
+ hook: hook,
392
+ category: post.category,
393
+ minimumPrice: post.minimumPrice,
394
+ minimumTotalSupply: post.minimumTotalSupply,
395
+ maximumTotalSupply: post.maximumTotalSupply,
396
+ maximumSplitPercent: post.maximumSplitPercent,
397
+ allowedAddresses: post.allowedAddresses
398
+ });
399
+ }
400
+
401
+ // Set up the allowed posts in the publisher.
402
+ PUBLISHER.configurePostingCriteriaFor({allowedPosts: formattedAllowedPosts});
403
+ }
404
+
405
+ //*********************************************************************//
406
+ // ------------------------ internal functions ----------------------- //
407
+ //*********************************************************************//
408
+
409
+ /// @notice The calldata. Preferred to use over `msg.data`.
410
+ /// @return calldata The `msg.data` of this call.
411
+ function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
412
+ return ERC2771Context._msgData();
413
+ }
414
+
415
+ /// @notice The message's sender. Preferred to use over `msg.sender`.
416
+ /// @return sender The address which sent this call.
417
+ function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
418
+ return ERC2771Context._msgSender();
419
+ }
420
+
421
+ /// @dev ERC-2771 specifies the context as being a single address (20 bytes).
422
+ function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
423
+ return ERC2771Context._contextSuffixLength();
424
+ }
425
+ }
@@ -0,0 +1,78 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.23;
3
+
4
+ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
5
+ import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
6
+ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
7
+ import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
8
+ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
9
+
10
+ import {ICTProjectOwner} from "./interfaces/ICTProjectOwner.sol";
11
+ import {ICTPublisher} from "./interfaces/ICTPublisher.sol";
12
+
13
+ /// @notice A contract that can be sent a project to be burned, while still allowing croptop posts.
14
+ contract CTProjectOwner is IERC721Receiver, ICTProjectOwner {
15
+ //*********************************************************************//
16
+ // ---------------- public immutable stored properties --------------- //
17
+ //*********************************************************************//
18
+
19
+ /// @notice The contract where operator permissions are stored.
20
+ IJBPermissions public immutable override PERMISSIONS;
21
+
22
+ /// @notice The contract from which project are minted.
23
+ IJBProjects public immutable override PROJECTS;
24
+
25
+ /// @notice The Croptop publisher.
26
+ ICTPublisher public immutable override PUBLISHER;
27
+
28
+ //*********************************************************************//
29
+ // -------------------------- constructor ---------------------------- //
30
+ //*********************************************************************//
31
+
32
+ /// @param permissions The contract where operator permissions are stored.
33
+ /// @param projects The contract from which project are minted.
34
+ /// @param publisher The Croptop publisher.
35
+ constructor(IJBPermissions permissions, IJBProjects projects, ICTPublisher publisher) {
36
+ PERMISSIONS = permissions;
37
+ PROJECTS = projects;
38
+ PUBLISHER = publisher;
39
+ }
40
+
41
+ //*********************************************************************//
42
+ // ---------------------- external transactions ---------------------- //
43
+ //*********************************************************************//
44
+
45
+ /// @notice Give the croptop publisher permission to post to the project on this contract's behalf.
46
+ /// @dev Make sure to first configure certain posts before sending this contract ownership.
47
+ function onERC721Received(
48
+ address operator,
49
+ address from,
50
+ uint256 tokenId,
51
+ bytes calldata data
52
+ )
53
+ external
54
+ override
55
+ returns (bytes4)
56
+ {
57
+ data;
58
+ from;
59
+ operator;
60
+
61
+ // Make sure the 721 received is the JBProjects contract.
62
+ if (msg.sender != address(PROJECTS)) revert();
63
+
64
+ // Set the correct permission.
65
+ uint8[] memory permissionIds = new uint8[](1);
66
+ permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
67
+
68
+ // Give the croptop contract permission to post on this contract's behalf.
69
+ PERMISSIONS.setPermissionsFor({
70
+ account: address(this),
71
+ permissionsData: JBPermissionsData({
72
+ operator: address(PUBLISHER), projectId: uint56(tokenId), permissionIds: permissionIds
73
+ })
74
+ });
75
+
76
+ return IERC721Receiver.onERC721Received.selector;
77
+ }
78
+ }