@croptop/core-v6 0.0.27 → 0.0.29
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/ADMINISTRATION.md +30 -3
- package/ARCHITECTURE.md +49 -157
- package/AUDIT_INSTRUCTIONS.md +69 -485
- package/CHANGELOG.md +57 -0
- package/README.md +54 -137
- package/RISKS.md +27 -3
- package/SKILLS.md +28 -187
- package/STYLE_GUIDE.md +56 -17
- package/USER_JOURNEYS.md +37 -708
- package/package.json +5 -6
- package/references/operations.md +25 -0
- package/references/runtime.md +27 -0
- package/script/Deploy.s.sol +4 -23
- package/src/CTDeployer.sol +5 -1
- package/src/CTPublisher.sol +16 -15
- package/test/CTPublisher.t.sol +10 -7
- package/test/audit/DeployerPermissionBypass.t.sol +213 -0
- package/test/audit/FeeFallbackBlackhole.t.sol +263 -0
- package/test/regression/FeeEvasion.t.sol +15 -10
- package/test/regression/StaleTierIdMapping.t.sol +8 -5
- package/CHANGE_LOG.md +0 -273
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@croptop/core-v6",
|
|
3
|
-
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-v5'"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
20
|
-
"@bananapus/
|
|
21
|
-
"@bananapus/
|
|
22
|
-
"@bananapus/core-v6": "^0.0.30",
|
|
19
|
+
"@bananapus/721-hook-v6": "^0.0.30",
|
|
20
|
+
"@bananapus/buyback-hook-v6": "^0.0.25",
|
|
21
|
+
"@bananapus/core-v6": "^0.0.31",
|
|
23
22
|
"@bananapus/ownable-v6": "^0.0.16",
|
|
24
23
|
"@bananapus/permission-ids-v6": "^0.0.15",
|
|
25
|
-
"@bananapus/router-terminal-v6": "^0.0.
|
|
24
|
+
"@bananapus/router-terminal-v6": "^0.0.25",
|
|
26
25
|
"@bananapus/suckers-v6": "^0.0.20",
|
|
27
26
|
"@openzeppelin/contracts": "^5.6.1"
|
|
28
27
|
},
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Croptop Operations
|
|
2
|
+
|
|
3
|
+
## Deployment Surface
|
|
4
|
+
|
|
5
|
+
- [`src/CTDeployer.sol`](../src/CTDeployer.sol) is the first stop for project launch shape, hook forwarding, and optional sucker integration.
|
|
6
|
+
- [`script/Deploy.s.sol`](../script/Deploy.s.sol) and [`script/ConfigureFeeProject.s.sol`](../script/ConfigureFeeProject.s.sol) cover the current deployment and fee-project wiring.
|
|
7
|
+
- [`src/structs/`](../src/structs/) contains config types that often drift from memory.
|
|
8
|
+
|
|
9
|
+
## Change Checklist
|
|
10
|
+
|
|
11
|
+
- If you edit posting criteria, verify both direct publisher calls and deployer-created project flows.
|
|
12
|
+
- If you edit fee behavior, check both the designated fee project path and any exemption behavior.
|
|
13
|
+
- If you edit burn-lock ownership assumptions, confirm the intended irreversibility still holds.
|
|
14
|
+
- If you edit data-hook forwarding, re-check sucker-related fee-free cash-out behavior.
|
|
15
|
+
|
|
16
|
+
## Common Failure Modes
|
|
17
|
+
|
|
18
|
+
- Publishing bug is blamed on the publisher when the deployer packaged the project or hook incorrectly.
|
|
19
|
+
- Immutable-owner expectations are missed after ownership moves into [`src/CTProjectOwner.sol`](../src/CTProjectOwner.sol).
|
|
20
|
+
- Content reuse or duplicate-post behavior changes and silently alters user-facing publishing semantics.
|
|
21
|
+
|
|
22
|
+
## Useful Proof Points
|
|
23
|
+
|
|
24
|
+
- [`test/fork/`](../test/fork/) when deployment shape matters.
|
|
25
|
+
- [`script/helpers/`](../script/helpers/) if the issue is really script/config assembly.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Croptop Runtime
|
|
2
|
+
|
|
3
|
+
## Contract Roles
|
|
4
|
+
|
|
5
|
+
- [`src/CTPublisher.sol`](../src/CTPublisher.sol) validates posts, configures or reuses tiers, mints first copies, and routes Croptop fees.
|
|
6
|
+
- [`src/CTDeployer.sol`](../src/CTDeployer.sol) packages project deployment, hook forwarding, and optional sucker support.
|
|
7
|
+
- [`src/CTProjectOwner.sol`](../src/CTProjectOwner.sol) is the burn-lock ownership helper for immutable administration patterns.
|
|
8
|
+
|
|
9
|
+
## Runtime Path
|
|
10
|
+
|
|
11
|
+
1. A project is deployed or configured with Croptop posting rules.
|
|
12
|
+
2. Publishers call into [`src/CTPublisher.sol`](../src/CTPublisher.sol) with content, supply, and pricing data.
|
|
13
|
+
3. The publisher validates category-level rules, creates or reuses tiers, mints the first copy, and routes fees and proceeds.
|
|
14
|
+
4. If the project uses the deployer wrapper, data-hook calls forward through [`src/CTDeployer.sol`](../src/CTDeployer.sol).
|
|
15
|
+
|
|
16
|
+
## High-Risk Areas
|
|
17
|
+
|
|
18
|
+
- Posting criteria: category rules are the policy surface that protects the project from bad content or bad economics.
|
|
19
|
+
- Fee routing: fee-project assumptions and fee exemptions are operationally important.
|
|
20
|
+
- Tier reuse and duplicate content: content identity is part of runtime behavior, not just metadata.
|
|
21
|
+
- Burn-lock ownership: once ownership moves into the lock helper, reversibility expectations change drastically.
|
|
22
|
+
|
|
23
|
+
## Tests To Trust First
|
|
24
|
+
|
|
25
|
+
- [`test/regression/`](../test/regression/) for pinned content and tier edge cases.
|
|
26
|
+
- [`test/fork/`](../test/fork/) for live integration assumptions.
|
|
27
|
+
- [`test/`](../test/) broadly when the issue could be in publisher or deployer behavior rather than one isolated function.
|
package/script/Deploy.s.sol
CHANGED
|
@@ -64,29 +64,10 @@ contract DeployScript is Script, Sphinx {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function deploy() public sphinx {
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
// Check if the publisher already exists by scanning existing project IDs.
|
|
72
|
-
uint256 _existingCount = core.projects.count();
|
|
73
|
-
bool _found;
|
|
74
|
-
for (uint256 _candidateId = 1; _candidateId <= _existingCount; _candidateId++) {
|
|
75
|
-
(, bool _exists) = _isDeployed({
|
|
76
|
-
salt: PUBLISHER_SALT,
|
|
77
|
-
creationCode: type(CTPublisher).creationCode,
|
|
78
|
-
arguments: abi.encode(core.directory, core.permissions, _candidateId, TRUSTED_FORWARDER)
|
|
79
|
-
});
|
|
80
|
-
if (_exists) {
|
|
81
|
-
FEE_PROJECT_ID = _candidateId;
|
|
82
|
-
_found = true;
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (!_found) {
|
|
87
|
-
FEE_PROJECT_ID = core.projects.createFor(safeAddress());
|
|
88
|
-
}
|
|
89
|
-
}
|
|
67
|
+
// Canonical Croptop deployments must bind fees to an explicit fee project. Autodiscovering the first
|
|
68
|
+
// matching publisher by scanning project IDs is unsafe because a preexisting publisher can pin fees to
|
|
69
|
+
// the wrong project forever.
|
|
70
|
+
require(FEE_PROJECT_ID != 0, "explicit fee project id required");
|
|
90
71
|
|
|
91
72
|
CTPublisher publisher;
|
|
92
73
|
{
|
package/src/CTDeployer.sol
CHANGED
|
@@ -248,6 +248,9 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
/// @notice Deploy a simple project meant to receive posts from Croptop templates.
|
|
251
|
+
/// @dev The initial project owner is intentionally granted direct hook-management permissions from
|
|
252
|
+
/// `CTDeployer`. This means the owner/operator can bypass the Croptop publisher path and interact
|
|
253
|
+
/// with the hook directly if they choose to. That is an explicit product tradeoff.
|
|
251
254
|
/// @param owner The address that'll own the project.
|
|
252
255
|
/// @param projectConfig The configuration for the project.
|
|
253
256
|
/// @param suckerDeploymentConfiguration The configuration for the suckers to deploy.
|
|
@@ -342,7 +345,8 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
342
345
|
// These permissions are granted from CTDeployer (address(this)) to the initial owner.
|
|
343
346
|
// The hook checks permissions against hook.owner(), which after claimCollectionOwnershipOf() resolves
|
|
344
347
|
// dynamically via PROJECTS.ownerOf(projectId). Before claiming, CTDeployer is the static hook owner,
|
|
345
|
-
// so these permissions allow the project owner to manage tiers through CTDeployer.
|
|
348
|
+
// so these permissions allow the project owner to manage tiers through CTDeployer. As a tradeoff,
|
|
349
|
+
// the owner can also bypass the Croptop publisher surface until ownership is claimed away.
|
|
346
350
|
uint8[] memory permissionIds = new uint8[](4);
|
|
347
351
|
permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
|
|
348
352
|
permissionIds[1] = JBPermissionIds.SET_721_METADATA;
|
package/src/CTPublisher.sol
CHANGED
|
@@ -5,6 +5,7 @@ import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721Tiers
|
|
|
5
5
|
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
6
6
|
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
7
7
|
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
8
|
+
import {JB721TierConfigFlags} from "@bananapus/721-hook-v6/src/structs/JB721TierConfigFlags.sol";
|
|
8
9
|
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
9
10
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
10
11
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
@@ -33,6 +34,7 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
33
34
|
error CTPublisher_MaxTotalSupplyLessThanMin(uint256 min, uint256 max);
|
|
34
35
|
error CTPublisher_NotInAllowList(address addr, address[] allowedAddresses);
|
|
35
36
|
error CTPublisher_PriceTooSmall(uint256 price, uint256 minimumPrice);
|
|
37
|
+
error CTPublisher_FeePaymentFailed(uint256 feeAmount);
|
|
36
38
|
error CTPublisher_SplitPercentExceedsMaximum(uint256 splitPercent, uint256 maximumSplitPercent);
|
|
37
39
|
error CTPublisher_TotalSupplyTooBig(uint256 totalSupply, uint256 maximumTotalSupply);
|
|
38
40
|
error CTPublisher_TotalSupplyTooSmall(uint256 totalSupply, uint256 minimumTotalSupply);
|
|
@@ -416,7 +418,8 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
416
418
|
IJBTerminal feeTerminal =
|
|
417
419
|
DIRECTORY.primaryTerminalOf({projectId: FEE_PROJECT_ID, token: JBConstants.NATIVE_TOKEN});
|
|
418
420
|
|
|
419
|
-
// Make the fee payment.
|
|
421
|
+
// Make the fee payment. If the fee sink is unavailable, refund the fee to the caller
|
|
422
|
+
// rather than trapping or silently redirecting protocol funds.
|
|
420
423
|
// slither-disable-next-line unused-return
|
|
421
424
|
try feeTerminal.pay{value: payValue}({
|
|
422
425
|
projectId: FEE_PROJECT_ID,
|
|
@@ -428,13 +431,9 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
428
431
|
metadata: feeMetadata
|
|
429
432
|
}) {}
|
|
430
433
|
catch {
|
|
431
|
-
//
|
|
432
|
-
(bool success,) =
|
|
433
|
-
if (!success)
|
|
434
|
-
// If that also fails, send to the msg.sender.
|
|
435
|
-
// slither-disable-next-line low-level-calls
|
|
436
|
-
(success,) = msg.sender.call{value: payValue}("");
|
|
437
|
-
}
|
|
434
|
+
// slither-disable-next-line low-level-calls
|
|
435
|
+
(bool success,) = _msgSender().call{value: payValue}("");
|
|
436
|
+
if (!success) revert CTPublisher_FeePaymentFailed(payValue);
|
|
438
437
|
}
|
|
439
438
|
}
|
|
440
439
|
}
|
|
@@ -569,13 +568,15 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
569
568
|
encodedIPFSUri: post.encodedIPFSUri,
|
|
570
569
|
category: post.category,
|
|
571
570
|
discountPercent: 0,
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
571
|
+
flags: JB721TierConfigFlags({
|
|
572
|
+
allowOwnerMint: false,
|
|
573
|
+
useReserveBeneficiaryAsDefault: false,
|
|
574
|
+
transfersPausable: false,
|
|
575
|
+
useVotingUnits: true,
|
|
576
|
+
cantBeRemoved: false,
|
|
577
|
+
cantIncreaseDiscountPercent: false,
|
|
578
|
+
cantBuyWithCredits: false
|
|
579
|
+
}),
|
|
579
580
|
splitPercent: post.splitPercent,
|
|
580
581
|
splits: post.splits
|
|
581
582
|
});
|
package/test/CTPublisher.t.sol
CHANGED
|
@@ -15,6 +15,7 @@ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
|
15
15
|
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
16
16
|
|
|
17
17
|
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
18
|
+
import {JB721TierConfigFlags} from "@bananapus/721-hook-v6/src/structs/JB721TierConfigFlags.sol";
|
|
18
19
|
|
|
19
20
|
import {CTPublisher} from "../src/CTPublisher.sol";
|
|
20
21
|
import {CTAllowedPost} from "../src/structs/CTAllowedPost.sol";
|
|
@@ -809,13 +810,15 @@ contract TestCTPublisher is Test {
|
|
|
809
810
|
encodedIPFSUri: keccak256("split-beneficiary-test"),
|
|
810
811
|
category: 5,
|
|
811
812
|
discountPercent: 0,
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
813
|
+
flags: JB721TierConfigFlags({
|
|
814
|
+
allowOwnerMint: false,
|
|
815
|
+
useReserveBeneficiaryAsDefault: false,
|
|
816
|
+
transfersPausable: false,
|
|
817
|
+
useVotingUnits: true,
|
|
818
|
+
cantBeRemoved: false,
|
|
819
|
+
cantIncreaseDiscountPercent: false,
|
|
820
|
+
cantBuyWithCredits: false
|
|
821
|
+
}),
|
|
819
822
|
splitPercent: 250_000_000,
|
|
820
823
|
splits: splits
|
|
821
824
|
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
+
import "forge-std/Test.sol";
|
|
6
|
+
|
|
7
|
+
import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
|
|
8
|
+
import {JBDeploy721TiersHookConfig} from "@bananapus/721-hook-v6/src/structs/JBDeploy721TiersHookConfig.sol";
|
|
9
|
+
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
10
|
+
import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
|
|
11
|
+
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
12
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
13
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
14
|
+
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
15
|
+
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
16
|
+
import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
|
|
17
|
+
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
18
|
+
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
19
|
+
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
20
|
+
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
21
|
+
import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
|
|
22
|
+
|
|
23
|
+
import {CTDeployer} from "../../src/CTDeployer.sol";
|
|
24
|
+
import {CTPublisher} from "../../src/CTPublisher.sol";
|
|
25
|
+
import {ICTPublisher} from "../../src/interfaces/ICTPublisher.sol";
|
|
26
|
+
import {CTDeployerAllowedPost} from "../../src/structs/CTDeployerAllowedPost.sol";
|
|
27
|
+
import {CTProjectConfig} from "../../src/structs/CTProjectConfig.sol";
|
|
28
|
+
import {CTSuckerDeploymentConfig} from "../../src/structs/CTSuckerDeploymentConfig.sol";
|
|
29
|
+
|
|
30
|
+
contract MockProjects {
|
|
31
|
+
uint256 public countValue;
|
|
32
|
+
address public ownerOfProject;
|
|
33
|
+
|
|
34
|
+
function setCount(uint256 count_) external {
|
|
35
|
+
countValue = count_;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setOwner(address owner_) external {
|
|
39
|
+
ownerOfProject = owner_;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function count() external view returns (uint256) {
|
|
43
|
+
return countValue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ownerOf(uint256) external view returns (address) {
|
|
47
|
+
return ownerOfProject;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function transferFrom(address, address to, uint256) external {
|
|
51
|
+
ownerOfProject = to;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
contract MockController {
|
|
56
|
+
MockProjects public immutable PROJECTS;
|
|
57
|
+
// forge-lint: disable-next-line(screaming-snake-case-immutable)
|
|
58
|
+
uint256 public immutable nextProjectId;
|
|
59
|
+
|
|
60
|
+
constructor(MockProjects projects_, uint256 nextProjectId_) {
|
|
61
|
+
PROJECTS = projects_;
|
|
62
|
+
nextProjectId = nextProjectId_;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function launchProjectFor(
|
|
66
|
+
address,
|
|
67
|
+
string calldata,
|
|
68
|
+
JBRulesetConfig[] calldata,
|
|
69
|
+
JBTerminalConfig[] calldata,
|
|
70
|
+
string calldata
|
|
71
|
+
)
|
|
72
|
+
external
|
|
73
|
+
view
|
|
74
|
+
returns (uint256)
|
|
75
|
+
{
|
|
76
|
+
return nextProjectId;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
contract MockSuckerRegistry {
|
|
81
|
+
function isSuckerOf(uint256, address) external pure returns (bool) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function deploySuckersFor(
|
|
86
|
+
uint256,
|
|
87
|
+
bytes32,
|
|
88
|
+
JBSuckerDeployerConfig[] calldata
|
|
89
|
+
)
|
|
90
|
+
external
|
|
91
|
+
pure
|
|
92
|
+
returns (address[] memory suckers)
|
|
93
|
+
{
|
|
94
|
+
return suckers;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
contract PermissionedHook is JBPermissioned {
|
|
99
|
+
// forge-lint: disable-next-line(screaming-snake-case-immutable)
|
|
100
|
+
address public immutable ownerAccount;
|
|
101
|
+
// forge-lint: disable-next-line(screaming-snake-case-immutable)
|
|
102
|
+
uint256 public immutable projectId;
|
|
103
|
+
bool public adjusted;
|
|
104
|
+
|
|
105
|
+
constructor(IJBPermissions permissions, address ownerAccount_, uint256 projectId_) JBPermissioned(permissions) {
|
|
106
|
+
ownerAccount = ownerAccount_;
|
|
107
|
+
projectId = projectId_;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
111
|
+
function PROJECT_ID() external view returns (uint256) {
|
|
112
|
+
return projectId;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function owner() external view returns (address) {
|
|
116
|
+
return ownerAccount;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function adjustTiers(JB721TierConfig[] calldata, uint256[] calldata) external {
|
|
120
|
+
_requirePermissionFrom(ownerAccount, projectId, JBPermissionIds.ADJUST_721_TIERS);
|
|
121
|
+
adjusted = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
contract MockHookDeployer {
|
|
126
|
+
IJB721TiersHook public hook;
|
|
127
|
+
|
|
128
|
+
function setHook(IJB721TiersHook hook_) external {
|
|
129
|
+
hook = hook_;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function deployHookFor(
|
|
133
|
+
uint256,
|
|
134
|
+
JBDeploy721TiersHookConfig calldata,
|
|
135
|
+
bytes32
|
|
136
|
+
)
|
|
137
|
+
external
|
|
138
|
+
view
|
|
139
|
+
returns (IJB721TiersHook)
|
|
140
|
+
{
|
|
141
|
+
return hook;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
contract DeployerPermissionBypassTest is Test {
|
|
146
|
+
JBPermissions permissions;
|
|
147
|
+
MockProjects projects;
|
|
148
|
+
MockHookDeployer hookDeployer;
|
|
149
|
+
MockSuckerRegistry suckerRegistry;
|
|
150
|
+
MockController controller;
|
|
151
|
+
CTPublisher publisher;
|
|
152
|
+
CTDeployer deployer;
|
|
153
|
+
PermissionedHook hook;
|
|
154
|
+
|
|
155
|
+
address owner = makeAddr("owner");
|
|
156
|
+
|
|
157
|
+
function setUp() public {
|
|
158
|
+
permissions = new JBPermissions(address(0));
|
|
159
|
+
projects = new MockProjects();
|
|
160
|
+
projects.setCount(5);
|
|
161
|
+
hookDeployer = new MockHookDeployer();
|
|
162
|
+
suckerRegistry = new MockSuckerRegistry();
|
|
163
|
+
publisher = new CTPublisher(IJBDirectory(makeAddr("directory")), permissions, 1, address(0));
|
|
164
|
+
deployer = new CTDeployer(
|
|
165
|
+
permissions,
|
|
166
|
+
IJBProjects(address(projects)),
|
|
167
|
+
IJB721TiersHookDeployer(address(hookDeployer)),
|
|
168
|
+
ICTPublisher(address(publisher)),
|
|
169
|
+
IJBSuckerRegistry(address(suckerRegistry)),
|
|
170
|
+
address(0)
|
|
171
|
+
);
|
|
172
|
+
hook = new PermissionedHook(permissions, address(deployer), 6);
|
|
173
|
+
hookDeployer.setHook(IJB721TiersHook(address(hook)));
|
|
174
|
+
controller = new MockController(projects, 6);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function test_projectOwnerCanBypassCroptopAndCallHookDirectlyAfterDeployment() public {
|
|
178
|
+
CTDeployerAllowedPost[] memory allowedPosts = new CTDeployerAllowedPost[](1);
|
|
179
|
+
allowedPosts[0] = CTDeployerAllowedPost({
|
|
180
|
+
category: 1,
|
|
181
|
+
minimumPrice: 1 ether,
|
|
182
|
+
minimumTotalSupply: 10,
|
|
183
|
+
maximumTotalSupply: 10,
|
|
184
|
+
maximumSplitPercent: 0,
|
|
185
|
+
allowedAddresses: new address[](0)
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
CTProjectConfig memory config = CTProjectConfig({
|
|
189
|
+
terminalConfigurations: new JBTerminalConfig[](0),
|
|
190
|
+
projectUri: "ipfs://project",
|
|
191
|
+
allowedPosts: allowedPosts,
|
|
192
|
+
contractUri: "ipfs://contract",
|
|
193
|
+
name: "Croptop",
|
|
194
|
+
symbol: "CT",
|
|
195
|
+
salt: bytes32(0)
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
CTSuckerDeploymentConfig memory suckerConfig =
|
|
199
|
+
CTSuckerDeploymentConfig({deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32(0)});
|
|
200
|
+
|
|
201
|
+
deployer.deployProjectFor(owner, config, suckerConfig, IJBController(address(controller)));
|
|
202
|
+
|
|
203
|
+
assertEq(projects.ownerOf(6), owner, "deployment should hand the project NFT to the owner");
|
|
204
|
+
|
|
205
|
+
JB721TierConfig[] memory arbitraryTiers = new JB721TierConfig[](0);
|
|
206
|
+
uint256[] memory removals = new uint256[](0);
|
|
207
|
+
|
|
208
|
+
vm.prank(owner);
|
|
209
|
+
PermissionedHook(address(hook)).adjustTiers(arbitraryTiers, removals);
|
|
210
|
+
|
|
211
|
+
assertTrue(hook.adjusted(), "project owner can mutate the hook without going through Croptop");
|
|
212
|
+
}
|
|
213
|
+
}
|