@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
package/README.md
CHANGED
package/foundry.toml
CHANGED
|
@@ -17,7 +17,8 @@ fail_on_revert = false
|
|
|
17
17
|
ethereum = "${RPC_ETHEREUM_MAINNET}"
|
|
18
18
|
|
|
19
19
|
[lint]
|
|
20
|
-
exclude_lints = ["
|
|
20
|
+
exclude_lints = ["mixed-case-variable", "pascal-case-struct"]
|
|
21
|
+
|
|
21
22
|
[fmt]
|
|
22
23
|
number_underscore = "thousands"
|
|
23
24
|
multiline_func_header = "all"
|
package/package.json
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@croptop/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/mejango/croptop-core-v6"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"foundry.toml",
|
|
12
|
+
"references/",
|
|
13
|
+
"remappings.txt",
|
|
14
|
+
"script/ConfigureFeeProject.s.sol",
|
|
15
|
+
"script/Deploy.s.sol",
|
|
16
|
+
"script/helpers/",
|
|
17
|
+
"src/"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20.0.0"
|
|
21
|
+
},
|
|
9
22
|
"scripts": {
|
|
10
23
|
"test": "forge test",
|
|
11
24
|
"coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
|
|
@@ -13,21 +26,20 @@
|
|
|
13
26
|
"deploy:mainnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks mainnets",
|
|
14
27
|
"deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
|
|
15
28
|
"deploy:testnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks testnets",
|
|
16
|
-
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-
|
|
29
|
+
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-v6'"
|
|
17
30
|
},
|
|
18
31
|
"dependencies": {
|
|
19
|
-
"@bananapus/721-hook-v6": "
|
|
20
|
-
"@bananapus/
|
|
21
|
-
"@bananapus/
|
|
22
|
-
"@bananapus/
|
|
23
|
-
"@bananapus/
|
|
24
|
-
"@bananapus/
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
32
|
+
"@bananapus/721-hook-v6": "0.0.43",
|
|
33
|
+
"@bananapus/core-v6": "0.0.39",
|
|
34
|
+
"@bananapus/ownable-v6": "0.0.24",
|
|
35
|
+
"@bananapus/permission-ids-v6": "0.0.22",
|
|
36
|
+
"@bananapus/router-terminal-v6": "0.0.36",
|
|
37
|
+
"@bananapus/suckers-v6": "0.0.33",
|
|
38
|
+
"@openzeppelin/contracts": "5.6.1",
|
|
39
|
+
"@rev-net/core-v6": "0.0.37"
|
|
27
40
|
},
|
|
28
41
|
"devDependencies": {
|
|
29
|
-
"@bananapus/address-registry-v6": "
|
|
30
|
-
"@
|
|
31
|
-
"@sphinx-labs/plugins": "^0.33.3"
|
|
42
|
+
"@bananapus/address-registry-v6": "0.0.25",
|
|
43
|
+
"@sphinx-labs/plugins": "0.33.3"
|
|
32
44
|
}
|
|
33
45
|
}
|
|
@@ -248,14 +248,16 @@ contract ConfigureFeeProjectScript is Script, Sphinx {
|
|
|
248
248
|
if (block.chainid == 1 || block.chainid == 11_155_111) {
|
|
249
249
|
suckerDeployerConfigurations = new JBSuckerDeployerConfig[](3);
|
|
250
250
|
// OP
|
|
251
|
-
suckerDeployerConfigurations[0] =
|
|
252
|
-
|
|
251
|
+
suckerDeployerConfigurations[0] = JBSuckerDeployerConfig({
|
|
252
|
+
deployer: suckers.optimismDeployer, peer: bytes32(0), mappings: tokenMappings
|
|
253
|
+
});
|
|
253
254
|
|
|
254
255
|
suckerDeployerConfigurations[1] =
|
|
255
|
-
JBSuckerDeployerConfig({deployer: suckers.baseDeployer, mappings: tokenMappings});
|
|
256
|
+
JBSuckerDeployerConfig({deployer: suckers.baseDeployer, peer: bytes32(0), mappings: tokenMappings});
|
|
256
257
|
|
|
257
|
-
suckerDeployerConfigurations[2] =
|
|
258
|
-
|
|
258
|
+
suckerDeployerConfigurations[2] = JBSuckerDeployerConfig({
|
|
259
|
+
deployer: suckers.arbitrumDeployer, peer: bytes32(0), mappings: tokenMappings
|
|
260
|
+
});
|
|
259
261
|
} else {
|
|
260
262
|
suckerDeployerConfigurations = new JBSuckerDeployerConfig[](1);
|
|
261
263
|
// L2 -> Mainnet
|
|
@@ -263,6 +265,7 @@ contract ConfigureFeeProjectScript is Script, Sphinx {
|
|
|
263
265
|
deployer: address(suckers.optimismDeployer) != address(0)
|
|
264
266
|
? suckers.optimismDeployer
|
|
265
267
|
: address(suckers.baseDeployer) != address(0) ? suckers.baseDeployer : suckers.arbitrumDeployer,
|
|
268
|
+
peer: bytes32(0),
|
|
266
269
|
mappings: tokenMappings
|
|
267
270
|
});
|
|
268
271
|
|
package/src/CTDeployer.sol
CHANGED
|
@@ -36,24 +36,33 @@ import {CTDeployerAllowedPost} from "./structs/CTDeployerAllowedPost.sol";
|
|
|
36
36
|
import {CTProjectConfig} from "./structs/CTProjectConfig.sol";
|
|
37
37
|
import {CTSuckerDeploymentConfig} from "./structs/CTSuckerDeploymentConfig.sol";
|
|
38
38
|
|
|
39
|
-
/// @notice
|
|
39
|
+
/// @notice Deploys Juicebox projects pre-configured for Croptop — a permissionless NFT publishing system. Each
|
|
40
|
+
/// deployed project gets a tiered 721 hook (for minting posted NFTs), an optional set of cross-chain suckers, and this
|
|
41
|
+
/// contract set as the data hook so suckers get 0% cash-out tax and mint permission. The hook initially remains owned
|
|
42
|
+
/// by this deployer (allowing the publisher to add tiers); the project owner can later claim full hook ownership via
|
|
43
|
+
/// `claimCollectionOwnershipOf`.
|
|
40
44
|
contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC721Receiver, ICTDeployer {
|
|
41
45
|
//*********************************************************************//
|
|
42
46
|
// --------------------------- custom errors ------------------------- //
|
|
43
47
|
//*********************************************************************//
|
|
44
48
|
|
|
45
49
|
error CTDeployer_NotOwnerOfProject(uint256 projectId, address hook, address caller);
|
|
50
|
+
//*********************************************************************//
|
|
51
|
+
// ---------------------------- events -------------------------------- //
|
|
52
|
+
//*********************************************************************//
|
|
53
|
+
|
|
54
|
+
event CTDeployer_SuckerDeploymentFailed(uint256 indexed projectId, bytes32 indexed salt, bytes reason);
|
|
46
55
|
|
|
47
56
|
//*********************************************************************//
|
|
48
57
|
// ---------------- public immutable stored properties --------------- //
|
|
49
58
|
//*********************************************************************//
|
|
50
59
|
|
|
51
|
-
/// @notice Mints ERC-721s that represent Juicebox project ownership and transfers.
|
|
52
|
-
IJBProjects public immutable override PROJECTS;
|
|
53
|
-
|
|
54
60
|
/// @notice The deployer to launch Croptop recorded collections from.
|
|
55
61
|
IJB721TiersHookDeployer public immutable override DEPLOYER;
|
|
56
62
|
|
|
63
|
+
/// @notice Mints ERC-721s that represent Juicebox project ownership and transfers.
|
|
64
|
+
IJBProjects public immutable override PROJECTS;
|
|
65
|
+
|
|
57
66
|
/// @notice The Croptop publisher.
|
|
58
67
|
ICTPublisher public immutable override PUBLISHER;
|
|
59
68
|
|
|
@@ -95,25 +104,13 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
95
104
|
PUBLISHER = publisher;
|
|
96
105
|
SUCKER_REGISTRY = suckerRegistry;
|
|
97
106
|
|
|
98
|
-
//
|
|
107
|
+
// Set permission for the CTPublisher to adjust tiers while the deployer temporarily owns new hooks.
|
|
99
108
|
uint8[] memory permissionIds = new uint8[](1);
|
|
100
|
-
permissionIds[0] = JBPermissionIds.MAP_SUCKER_TOKEN;
|
|
101
|
-
|
|
102
|
-
// Give the operator the permission.
|
|
103
|
-
// Set up the permission data.
|
|
104
|
-
JBPermissionsData memory permissionData =
|
|
105
|
-
JBPermissionsData({operator: address(SUCKER_REGISTRY), projectId: 0, permissionIds: permissionIds});
|
|
106
|
-
|
|
107
|
-
// Set the permissions.
|
|
108
|
-
PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
|
|
109
|
-
|
|
110
|
-
// Set permission for the CTPublisher to adjust the tier.
|
|
111
109
|
permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
JBPermissionsData memory permissionData =
|
|
112
|
+
JBPermissionsData({operator: address(PUBLISHER), projectId: 0, permissionIds: permissionIds});
|
|
115
113
|
|
|
116
|
-
// Set permission for the CTPublisher to adjust the tier.
|
|
117
114
|
PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
|
|
118
115
|
}
|
|
119
116
|
|
|
@@ -137,7 +134,7 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
137
134
|
|
|
138
135
|
// Make sure the caller is the owner of the project.
|
|
139
136
|
if (PROJECTS.ownerOf(projectId) != _msgSender()) {
|
|
140
|
-
revert CTDeployer_NotOwnerOfProject(projectId, address(hook), _msgSender());
|
|
137
|
+
revert CTDeployer_NotOwnerOfProject({projectId: projectId, hook: address(hook), caller: _msgSender()});
|
|
141
138
|
}
|
|
142
139
|
|
|
143
140
|
// Transfer the hook's ownership to the project.
|
|
@@ -145,9 +142,9 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
145
142
|
}
|
|
146
143
|
|
|
147
144
|
/// @notice Deploy a simple project meant to receive posts from Croptop templates.
|
|
148
|
-
/// @dev The
|
|
149
|
-
///
|
|
150
|
-
///
|
|
145
|
+
/// @dev The deployed hook remains owned by `CTDeployer` until the project owner claims collection ownership.
|
|
146
|
+
/// The initial owner is granted direct deployer-scoped hook permissions as a launch-time convenience. Those
|
|
147
|
+
/// permissions can bypass Croptop's publisher surface until ownership is claimed away from the deployer.
|
|
151
148
|
/// @param owner The address that'll own the project.
|
|
152
149
|
/// @param projectConfig The configuration for the project.
|
|
153
150
|
/// @param suckerDeploymentConfiguration The configuration for the suckers to deploy.
|
|
@@ -170,8 +167,8 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
170
167
|
rulesetConfigurations[0].weight = 1_000_000 * (10 ** 18);
|
|
171
168
|
rulesetConfigurations[0].metadata.baseCurrency = JBCurrencyIds.ETH;
|
|
172
169
|
|
|
173
|
-
//
|
|
174
|
-
projectId = PROJECTS.
|
|
170
|
+
// Reserve the project ID up front so permissionless project creations cannot invalidate hook deployment.
|
|
171
|
+
projectId = PROJECTS.createFor(address(this));
|
|
175
172
|
|
|
176
173
|
// Deploy a blank project.
|
|
177
174
|
// slither-disable-next-line reentrancy-benign
|
|
@@ -202,17 +199,15 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
202
199
|
rulesetConfigurations[0].metadata.useDataHookForPay = true;
|
|
203
200
|
rulesetConfigurations[0].metadata.useDataHookForCashOut = true;
|
|
204
201
|
|
|
205
|
-
// Launch the
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
})
|
|
215
|
-
);
|
|
202
|
+
// Launch the rulesets for the reserved project.
|
|
203
|
+
// slither-disable-next-line unused-return
|
|
204
|
+
controller.launchRulesetsFor({
|
|
205
|
+
projectId: projectId,
|
|
206
|
+
projectUri: projectConfig.projectUri,
|
|
207
|
+
rulesetConfigurations: rulesetConfigurations,
|
|
208
|
+
terminalConfigurations: projectConfig.terminalConfigurations,
|
|
209
|
+
memo: "Deployed from Croptop"
|
|
210
|
+
});
|
|
216
211
|
|
|
217
212
|
// Set the data hook for the project.
|
|
218
213
|
dataHookOf[projectId] = IJBRulesetDataHook(hook);
|
|
@@ -227,23 +222,32 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
227
222
|
// intentionally ordered. If both deployers fail, the deployment proceeds without suckers rather than reverting,
|
|
228
223
|
// allowing projects to launch on unsupported chains with manual sucker setup later.
|
|
229
224
|
if (suckerDeploymentConfiguration.salt != bytes32(0)) {
|
|
225
|
+
bytes32 suckerSalt = keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender()));
|
|
226
|
+
|
|
227
|
+
// Successful deployments are discoverable from the registry, and failures are reported without reverting
|
|
228
|
+
// the project launch.
|
|
230
229
|
// slither-disable-next-line unused-return
|
|
231
|
-
SUCKER_REGISTRY.deploySuckersFor({
|
|
230
|
+
try SUCKER_REGISTRY.deploySuckersFor({
|
|
232
231
|
projectId: projectId,
|
|
233
|
-
salt:
|
|
232
|
+
salt: suckerSalt,
|
|
234
233
|
configurations: suckerDeploymentConfiguration.deployerConfigurations
|
|
235
|
-
})
|
|
234
|
+
}) returns (
|
|
235
|
+
address[] memory
|
|
236
|
+
) {
|
|
237
|
+
// no-op
|
|
238
|
+
}
|
|
239
|
+
catch (bytes memory reason) {
|
|
240
|
+
// slither-disable-next-line reentrancy-events
|
|
241
|
+
emit CTDeployer_SuckerDeploymentFailed({projectId: projectId, salt: suckerSalt, reason: reason});
|
|
242
|
+
}
|
|
236
243
|
}
|
|
237
244
|
|
|
238
|
-
//
|
|
245
|
+
// Transfer the project NFT to its intended owner.
|
|
239
246
|
PROJECTS.transferFrom({from: address(this), to: owner, tokenId: projectId});
|
|
240
247
|
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
// dynamically via PROJECTS.ownerOf(projectId). Before claiming, CTDeployer is the static hook owner,
|
|
245
|
-
// so these permissions allow the project owner to manage tiers through CTDeployer. As a tradeoff,
|
|
246
|
-
// the owner can also bypass the Croptop publisher surface until ownership is claimed away.
|
|
248
|
+
// Give the initial project owner direct collection-control permissions while CTDeployer remains the hook's
|
|
249
|
+
// owner. This preserves the documented Croptop launch tradeoff: the owner can manage the collection directly
|
|
250
|
+
// before calling `claimCollectionOwnershipOf(...)`, after which hook permissions follow the project NFT owner.
|
|
247
251
|
uint8[] memory permissionIds = new uint8[](4);
|
|
248
252
|
permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
|
|
249
253
|
permissionIds[1] = JBPermissionIds.SET_721_METADATA;
|
|
@@ -253,7 +257,7 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
253
257
|
PERMISSIONS.setPermissionsFor({
|
|
254
258
|
account: address(this),
|
|
255
259
|
permissionsData: JBPermissionsData({
|
|
256
|
-
operator:
|
|
260
|
+
operator: owner,
|
|
257
261
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
258
262
|
projectId: uint64(projectId),
|
|
259
263
|
permissionIds: permissionIds
|
|
@@ -272,12 +276,13 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
272
276
|
external
|
|
273
277
|
returns (address[] memory suckers)
|
|
274
278
|
{
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
});
|
|
279
|
+
address owner = PROJECTS.ownerOf(projectId);
|
|
280
|
+
|
|
281
|
+
// First prove the external caller is allowed to request sucker deployment for the project owner.
|
|
282
|
+
_requirePermissionFrom({account: owner, projectId: projectId, permissionId: JBPermissionIds.DEPLOY_SUCKERS});
|
|
279
283
|
|
|
280
|
-
// Deploy the suckers.
|
|
284
|
+
// Deploy the suckers. The sucker registry performs its own permission check against this forwarding helper,
|
|
285
|
+
// so an unapproved CTDeployer fails at the downstream registry boundary without an extra preflight read here.
|
|
281
286
|
// slither-disable-next-line unused-return
|
|
282
287
|
suckers = SUCKER_REGISTRY.deploySuckersFor({
|
|
283
288
|
projectId: projectId,
|
|
@@ -290,8 +295,10 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
290
295
|
// ------------------------- external views -------------------------- //
|
|
291
296
|
//*********************************************************************//
|
|
292
297
|
|
|
293
|
-
/// @notice
|
|
294
|
-
///
|
|
298
|
+
/// @notice Called before a cash out is recorded. Grants suckers 0% tax so bridged tokens redeem at full value.
|
|
299
|
+
/// For non-sucker holders, delegates to the project's stored data hook (if any) or passes through the original
|
|
300
|
+
/// context values.
|
|
301
|
+
/// @dev Part of `IJBRulesetDataHook`.
|
|
295
302
|
/// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
|
|
296
303
|
/// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
|
|
297
304
|
/// out.
|
|
@@ -331,8 +338,9 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
331
338
|
return hook.beforeCashOutRecordedWith(context);
|
|
332
339
|
}
|
|
333
340
|
|
|
334
|
-
/// @notice
|
|
335
|
-
///
|
|
341
|
+
/// @notice Called before a payment is recorded. Delegates to the project's stored data hook (the 721 hook) so NFT
|
|
342
|
+
/// tier minting logic runs. If no hook is set, passes through the original weight.
|
|
343
|
+
/// @dev Part of `IJBRulesetDataHook`.
|
|
336
344
|
/// @param context Standard Juicebox payment context. See `JBBeforePayRecordedContext`.
|
|
337
345
|
/// @return weight The weight which project tokens are minted relative to. This can be used to customize how many
|
|
338
346
|
/// tokens get minted by a payment.
|
|
@@ -354,8 +362,9 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
354
362
|
return hook.beforePayRecordedWith(context);
|
|
355
363
|
}
|
|
356
364
|
|
|
357
|
-
/// @notice
|
|
358
|
-
///
|
|
365
|
+
/// @notice Returns whether an address may mint a project's tokens on-demand. Only suckers get this permission, so
|
|
366
|
+
/// bridged tokens can be minted on the destination chain.
|
|
367
|
+
/// @dev Part of `IJBRulesetDataHook`.
|
|
359
368
|
/// @param projectId The ID of the project whose token can be minted.
|
|
360
369
|
/// @param addr The address to check the token minting permission of.
|
|
361
370
|
/// @return flag A flag indicating whether the address has permission to mint the project's tokens on-demand.
|
package/src/CTProjectOwner.sol
CHANGED
|
@@ -10,10 +10,12 @@ import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.
|
|
|
10
10
|
import {ICTProjectOwner} from "./interfaces/ICTProjectOwner.sol";
|
|
11
11
|
import {ICTPublisher} from "./interfaces/ICTPublisher.sol";
|
|
12
12
|
|
|
13
|
-
/// @notice A
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
///
|
|
13
|
+
/// @notice A dead-end owner for Juicebox projects that locks ownership while preserving Croptop posting. When a project
|
|
14
|
+
/// NFT is transferred to this contract via `safeTransferFrom`, it automatically grants the Croptop publisher
|
|
15
|
+
/// `ADJUST_721_TIERS` permission so posts can continue. The project can never be transferred out again — effectively
|
|
16
|
+
/// burning ownership while keeping the collection alive.
|
|
17
|
+
/// @dev This contract does not expose any function to reconfigure posting criteria. Criteria are set before
|
|
18
|
+
/// transferring the project here and become immutable once ownership is transferred.
|
|
17
19
|
contract CTProjectOwner is IERC721Receiver, ICTProjectOwner {
|
|
18
20
|
//*********************************************************************//
|
|
19
21
|
// ---------------- public immutable stored properties --------------- //
|
package/src/CTPublisher.sol
CHANGED
|
@@ -21,7 +21,11 @@ import {ICTPublisher} from "./interfaces/ICTPublisher.sol";
|
|
|
21
21
|
import {CTAllowedPost} from "./structs/CTAllowedPost.sol";
|
|
22
22
|
import {CTPost} from "./structs/CTPost.sol";
|
|
23
23
|
|
|
24
|
-
/// @notice
|
|
24
|
+
/// @notice The Croptop publishing engine. Allows anyone to publish NFT posts to a Juicebox project's 721 hook, subject
|
|
25
|
+
/// to per-category posting criteria set by the collection owner (minimum price, supply bounds, allowlist, max split
|
|
26
|
+
/// percent). On each mint, the publisher creates new 721 tiers, routes a 5% fee to the fee project, and pays the
|
|
27
|
+
/// remainder into the project's terminal. Duplicate IPFS URIs are tracked so subsequent mints of the same content reuse
|
|
28
|
+
/// the existing tier rather than creating a new one.
|
|
25
29
|
contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
26
30
|
//*********************************************************************//
|
|
27
31
|
// --------------------------- custom errors ------------------------- //
|
|
@@ -109,7 +113,10 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
109
113
|
// ---------------------- external transactions ---------------------- //
|
|
110
114
|
//*********************************************************************//
|
|
111
115
|
|
|
112
|
-
/// @notice
|
|
116
|
+
/// @notice Lets collection owners define the rules for what can be posted in each category — minimum price,
|
|
117
|
+
/// supply
|
|
118
|
+
/// bounds, maximum split percent, and an optional allowlist of addresses. Each call replaces the existing criteria
|
|
119
|
+
/// for the specified categories.
|
|
113
120
|
/// @param allowedPosts An array of criteria for allowed posts.
|
|
114
121
|
// Categories cannot be fully disabled after creation. This is by design — once a category is
|
|
115
122
|
// created, removing posting would break expectations for existing posters. Projects can set restrictive
|
|
@@ -171,8 +178,11 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
171
178
|
}
|
|
172
179
|
}
|
|
173
180
|
|
|
174
|
-
/// @notice Publish
|
|
175
|
-
///
|
|
181
|
+
/// @notice Publish one or more NFT posts to a project's 721 hook and mint a first copy of each. For each new post,
|
|
182
|
+
/// a tier is created on the hook. A 5% fee (1/FEE_DIVISOR) is taken from the total tier prices and routed to the
|
|
183
|
+
/// fee
|
|
184
|
+
/// project; the remainder is paid into the project's terminal, minting NFTs for the beneficiary.
|
|
185
|
+
/// @dev Reverts if any post violates the category's configured allowance (price, supply, split, allowlist).
|
|
176
186
|
/// @param hook The hook to mint from.
|
|
177
187
|
/// @param posts An array of posts that should be published as NFTs to the specified project.
|
|
178
188
|
/// @param nftBeneficiary The beneficiary of the NFT mints.
|
|
@@ -24,8 +24,8 @@ interface ICTDeployer {
|
|
|
24
24
|
function PUBLISHER() external view returns (ICTPublisher);
|
|
25
25
|
|
|
26
26
|
/// @notice Claim ownership of a tiered ERC-721 hook collection by transferring it to the project.
|
|
27
|
-
/// @dev After claiming,
|
|
28
|
-
///
|
|
27
|
+
/// @dev After claiming, hook ownership resolves through the current project NFT owner. That owner must grant
|
|
28
|
+
/// CTPublisher the ADJUST_721_TIERS permission separately for mintFrom() to continue working.
|
|
29
29
|
/// @param hook The hook to claim ownership of. The caller must own the project the hook belongs to.
|
|
30
30
|
function claimCollectionOwnershipOf(IJB721TiersHook hook) external;
|
|
31
31
|
|
|
@@ -4,13 +4,14 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
5
5
|
import {CTDeployerAllowedPost} from "../structs/CTDeployerAllowedPost.sol";
|
|
6
6
|
|
|
7
|
-
/// @
|
|
7
|
+
/// @notice Configuration for deploying a new Croptop project via CTDeployer.
|
|
8
|
+
/// @param terminalConfigurations The terminals that the project uses to accept payments through.
|
|
8
9
|
/// @param projectUri The metadata URI containing project info.
|
|
9
|
-
/// @param allowedPosts The
|
|
10
|
-
/// @param contractUri A link to the collection's metadata.
|
|
11
|
-
/// @param name The name of the collection where posts will
|
|
12
|
-
/// @param symbol The symbol of the collection where posts will
|
|
13
|
-
/// @param salt A salt
|
|
10
|
+
/// @param allowedPosts The posting criteria that define what kinds of NFTs can be published to each category.
|
|
11
|
+
/// @param contractUri A link to the 721 collection's metadata (e.g. OpenSea-compatible contract-level metadata).
|
|
12
|
+
/// @param name The name of the 721 collection where posts will be minted.
|
|
13
|
+
/// @param symbol The symbol of the 721 collection where posts will be minted.
|
|
14
|
+
/// @param salt A salt for deterministic deployment of the 721 hook (includes msg.sender for replay protection).
|
|
14
15
|
struct CTProjectConfig {
|
|
15
16
|
JBTerminalConfig[] terminalConfigurations;
|
|
16
17
|
string projectUri;
|
package/ADMINISTRATION.md
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Administration
|
|
2
|
-
|
|
3
|
-
## At A Glance
|
|
4
|
-
|
|
5
|
-
| Item | Details |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| Scope | Croptop deployment flow, publish-policy administration, and irreversible project owner sink behavior |
|
|
8
|
-
| Control posture | Mixed deployer-managed and project-local control |
|
|
9
|
-
| Highest-risk actions | Burn-locking a project into `CTProjectOwner`, misconfiguring posting criteria, and deploying suckers with the wrong authority assumptions |
|
|
10
|
-
| Recovery posture | Posting policy can often be changed, but burn-lock and some deployer wiring choices usually require replacement flows |
|
|
11
|
-
|
|
12
|
-
## Purpose
|
|
13
|
-
|
|
14
|
-
`croptop-core-v6` has two distinct control planes: project-local publishing control and deployer-level structural wiring. The high-risk surfaces are posting criteria, hook ownership, publisher permissions, and the irreversible `CTProjectOwner` burn-lock path.
|
|
15
|
-
|
|
16
|
-
## Control Model
|
|
17
|
-
|
|
18
|
-
- `CTPublisher` enforces publish policy but does not own the project.
|
|
19
|
-
- `CTDeployer` is both a deployment helper and a live ruleset data-hook wrapper.
|
|
20
|
-
- The initial project owner receives direct hook-management permissions from `CTDeployer` at deployment time.
|
|
21
|
-
- Project owners or delegates administer publishing through the hook owner and `JBPermissions`.
|
|
22
|
-
- `CTProjectOwner` is an irreversible ownership sink for projects that want Croptop-mediated control.
|
|
23
|
-
- `SUCKER_REGISTRY` and `PUBLISHER` receive structural permissions from `CTDeployer`.
|
|
24
|
-
|
|
25
|
-
## Roles
|
|
26
|
-
|
|
27
|
-
| Role | How Assigned | Scope | Notes |
|
|
28
|
-
| --- | --- | --- | --- |
|
|
29
|
-
| Project owner | `JBProjects.ownerOf(projectId)` | Per project | May grant delegates through `JBPermissions` |
|
|
30
|
-
| Hook owner | `JBOwnable(hook).owner()` | Per hook | Often resolves to the project owner after claim |
|
|
31
|
-
| `CTDeployer` | Immutable singleton | Global | Launch helper and runtime wrapper |
|
|
32
|
-
| `CTPublisher` | Immutable singleton | Global runtime surface | Needs `ADJUST_721_TIERS` authority on relevant hooks |
|
|
33
|
-
| `CTProjectOwner` | Receives project NFT transfer | Per project | Burn-lock path with no return function |
|
|
34
|
-
| `SUCKER_REGISTRY` | Immutable dependency | Global | Holds wildcard `MAP_SUCKER_TOKEN` from the deployer |
|
|
35
|
-
|
|
36
|
-
## Privileged Surfaces
|
|
37
|
-
|
|
38
|
-
| Contract | Function | Who Can Call | Effect |
|
|
39
|
-
| --- | --- | --- | --- |
|
|
40
|
-
| `CTDeployer` | `deployProjectFor(...)` | Anyone | Launches a Croptop-shaped project and configures initial permissions |
|
|
41
|
-
| `CTDeployer` | `claimCollectionOwnershipOf(...)` | Current project owner | Transfers hook ownership from the deployer path to the project |
|
|
42
|
-
| `CTDeployer` | `deploySuckersFor(...)` | Project owner or `DEPLOY_SUCKERS` delegate | Extends a project with suckers |
|
|
43
|
-
| `CTPublisher` | `configurePostingCriteriaFor(...)` | Hook owner or `ADJUST_721_TIERS` delegate | Changes posting policy for a hook and category |
|
|
44
|
-
| `CTPublisher` | `mintFrom(...)` | Anyone subject to policy | Publishes posts, mints first copies, and routes the Croptop fee |
|
|
45
|
-
| `CTProjectOwner` | `onERC721Received(...)` | Any project NFT transfer into it | Locks the project into the Croptop owner helper and grants `CTPublisher` tier-adjust authority |
|
|
46
|
-
|
|
47
|
-
Important nuance:
|
|
48
|
-
|
|
49
|
-
- after `deployProjectFor(...)`, the initial project owner can directly manage tiers, metadata, minting, and discount percent through permissions granted from `CTDeployer`
|
|
50
|
-
- that means the owner can bypass the publisher path until ownership is claimed away from `CTDeployer`
|
|
51
|
-
|
|
52
|
-
## Immutable And One-Way
|
|
53
|
-
|
|
54
|
-
- `CTDeployer`'s wildcard permission grants to `SUCKER_REGISTRY` and `CTPublisher` are structural.
|
|
55
|
-
- `dataHookOf[projectId]` is write-once through deployment flow.
|
|
56
|
-
- Sending a project NFT into `CTProjectOwner` is effectively irreversible.
|
|
57
|
-
- `FEE_PROJECT_ID` in `CTPublisher` is constructor-immutable.
|
|
58
|
-
|
|
59
|
-
## Operational Notes
|
|
60
|
-
|
|
61
|
-
- Validate posting criteria before broad publisher access.
|
|
62
|
-
- Decide intentionally whether the project should keep the initial direct-management path or move to project-owned hook control with `claimCollectionOwnershipOf(...)`.
|
|
63
|
-
- Use `claimCollectionOwnershipOf(...)` when the project should own the hook directly instead of relying on the deployer as the ownership bridge.
|
|
64
|
-
- Treat the burn-lock path as governance finality, not convenience.
|
|
65
|
-
- Review Croptop deployer changes as both launch-time and runtime changes.
|
|
66
|
-
|
|
67
|
-
## Machine Notes
|
|
68
|
-
|
|
69
|
-
- Do not treat `CTDeployer` as a passive script helper; it is also part of the live runtime path.
|
|
70
|
-
- Treat `src/CTPublisher.sol`, `src/CTDeployer.sol`, and `src/CTProjectOwner.sol` as the minimum source set for control-plane review.
|
|
71
|
-
- If a project NFT has already been sent to `CTProjectOwner`, stop assuming the original owner can recover it.
|
|
72
|
-
|
|
73
|
-
## Recovery
|
|
74
|
-
|
|
75
|
-
- If posting policy is wrong but the project still controls the hook, fix it through `configurePostingCriteriaFor(...)`.
|
|
76
|
-
- If the wrong hook path or burn-lock path was chosen, recovery usually means a new project or new hook arrangement.
|
|
77
|
-
- `CTProjectOwner` is not a reversible safety valve.
|
|
78
|
-
|
|
79
|
-
## Admin Boundaries
|
|
80
|
-
|
|
81
|
-
- Neither project owners nor Croptop can change the fixed fee divisor in `CTPublisher`.
|
|
82
|
-
- `CTPublisher` cannot trap fee ETH intentionally; failed fee-terminal payments refund `_msgSender()` or revert.
|
|
83
|
-
- `CTProjectOwner` cannot return project ownership once it receives the NFT.
|
|
84
|
-
- `CTDeployer` cannot later rewrite `dataHookOf[projectId]` through a setter.
|
|
85
|
-
- `CTDeployer` does not stop the initial project owner from using the directly granted hook permissions before ownership is claimed away.
|
|
86
|
-
|
|
87
|
-
## Source Map
|
|
88
|
-
|
|
89
|
-
- `src/CTPublisher.sol`
|
|
90
|
-
- `src/CTDeployer.sol`
|
|
91
|
-
- `src/CTProjectOwner.sol`
|
|
92
|
-
- `script/Deploy.s.sol`
|
|
93
|
-
- `script/helpers/CroptopDeploymentLib.sol`
|
|
94
|
-
- `test/TestAuditGaps.sol`
|