@croptop/core-v6 0.0.37 → 0.0.39
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 +52 -51
- package/src/CTPublisher.sol +20 -5
- package/src/interfaces/ICTDeployer.sol +2 -2
- 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 -203
- 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/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 -218
|
@@ -1,315 +0,0 @@
|
|
|
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 {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
8
|
-
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
9
|
-
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
10
|
-
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
11
|
-
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
12
|
-
import {IJBOwnable} from "@bananapus/ownable-v6/src/interfaces/IJBOwnable.sol";
|
|
13
|
-
import {IJB721Hook} from "@bananapus/721-hook-v6/src/interfaces/IJB721Hook.sol";
|
|
14
|
-
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
15
|
-
import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
|
|
16
|
-
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
17
|
-
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.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 {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
|
|
21
|
-
|
|
22
|
-
import {CTDeployer} from "../src/CTDeployer.sol";
|
|
23
|
-
import {CTPublisher} from "../src/CTPublisher.sol";
|
|
24
|
-
import {ICTPublisher} from "../src/interfaces/ICTPublisher.sol";
|
|
25
|
-
import {CTAllowedPost} from "../src/structs/CTAllowedPost.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
|
-
/// @title ClaimCollectionOwnershipTest
|
|
31
|
-
/// @notice Integration tests for the post-claimCollectionOwnership permission lifecycle:
|
|
32
|
-
/// 1. Deploy a croptop collection
|
|
33
|
-
/// 2. Claim ownership (transfers hook ownership to project)
|
|
34
|
-
/// 3. Verify permissions are correctly transferred
|
|
35
|
-
/// 4. Verify post-claim the hook owner changes and publisher permissions must be re-granted
|
|
36
|
-
contract ClaimCollectionOwnershipTest is Test {
|
|
37
|
-
CTDeployer ctDeployer;
|
|
38
|
-
CTPublisher publisher;
|
|
39
|
-
|
|
40
|
-
IJBPermissions permissions = IJBPermissions(makeAddr("permissions"));
|
|
41
|
-
IJBProjects projects = IJBProjects(makeAddr("projects"));
|
|
42
|
-
IJB721TiersHookDeployer hookDeployer = IJB721TiersHookDeployer(makeAddr("hookDeployer"));
|
|
43
|
-
IJBSuckerRegistry suckerRegistry = IJBSuckerRegistry(makeAddr("suckerRegistry"));
|
|
44
|
-
IJBController controller = IJBController(makeAddr("controller"));
|
|
45
|
-
|
|
46
|
-
address owner = makeAddr("owner");
|
|
47
|
-
address newOwner = makeAddr("newOwner");
|
|
48
|
-
address unauthorized = makeAddr("unauthorized");
|
|
49
|
-
address hookAddr = makeAddr("hook");
|
|
50
|
-
|
|
51
|
-
uint256 projectCount = 5;
|
|
52
|
-
uint256 deployedProjectId = projectCount + 1; // 6
|
|
53
|
-
uint256 feeProjectId = 1;
|
|
54
|
-
|
|
55
|
-
// Track which permissions were set.
|
|
56
|
-
PermissionRecord[] permissionRecords;
|
|
57
|
-
|
|
58
|
-
struct PermissionRecord {
|
|
59
|
-
address account;
|
|
60
|
-
address operator;
|
|
61
|
-
uint256 projectId;
|
|
62
|
-
uint8[] permissionIds;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function setUp() public {
|
|
66
|
-
// Mock permissions.setPermissionsFor (called in CTDeployer constructor + deployment).
|
|
67
|
-
vm.mockCall(
|
|
68
|
-
address(permissions), abi.encodeWithSelector(IJBPermissions.setPermissionsFor.selector), abi.encode()
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Mock permissions.hasPermission to return true by default.
|
|
72
|
-
vm.mockCall(
|
|
73
|
-
address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(true)
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// Mock sucker registry.
|
|
77
|
-
vm.mockCall(
|
|
78
|
-
address(suckerRegistry), abi.encodeWithSelector(IJBSuckerRegistry.isSuckerOf.selector), abi.encode(false)
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Deploy publisher.
|
|
82
|
-
publisher = new CTPublisher(IJBDirectory(makeAddr("directory")), permissions, feeProjectId, address(0));
|
|
83
|
-
|
|
84
|
-
// Deploy the CTDeployer.
|
|
85
|
-
ctDeployer = new CTDeployer(
|
|
86
|
-
permissions, projects, hookDeployer, ICTPublisher(address(publisher)), suckerRegistry, address(0)
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Fund test accounts.
|
|
90
|
-
vm.deal(owner, 100 ether);
|
|
91
|
-
vm.deal(newOwner, 100 ether);
|
|
92
|
-
vm.deal(unauthorized, 100 ether);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
//*********************************************************************//
|
|
96
|
-
// --- Full Lifecycle: Deploy -> Claim -> Verify ---------------------- //
|
|
97
|
-
//*********************************************************************//
|
|
98
|
-
|
|
99
|
-
/// @notice Tests the full lifecycle: deploy project, then claim collection ownership.
|
|
100
|
-
/// After claiming, the hook's owner should become the project (via transferOwnershipToProject).
|
|
101
|
-
function test_fullLifecycle_deploy_claim_ownershipTransfers() public {
|
|
102
|
-
// Step 1: Deploy the project.
|
|
103
|
-
_mockDeployProjectInfra();
|
|
104
|
-
|
|
105
|
-
CTProjectConfig memory config = _defaultProjectConfig();
|
|
106
|
-
CTSuckerDeploymentConfig memory suckerConfig = _emptySuckerConfig();
|
|
107
|
-
|
|
108
|
-
(uint256 projectId, IJB721TiersHook hook) = ctDeployer.deployProjectFor(owner, config, suckerConfig, controller);
|
|
109
|
-
assertEq(projectId, deployedProjectId, "project ID should match");
|
|
110
|
-
|
|
111
|
-
// Step 2: After deployment, the CTDeployer is the hook's owner.
|
|
112
|
-
// (The deployer owns the hook because it deployed it.)
|
|
113
|
-
// Mock hook.PROJECT_ID() to return deployedProjectId.
|
|
114
|
-
vm.mockCall(address(hook), abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(projectId));
|
|
115
|
-
|
|
116
|
-
// Mock PROJECTS.ownerOf(projectId) to return owner.
|
|
117
|
-
vm.mockCall(address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, projectId), abi.encode(owner));
|
|
118
|
-
|
|
119
|
-
// Mock JBOwnable.transferOwnershipToProject to succeed.
|
|
120
|
-
vm.mockCall(address(hook), abi.encodeWithSelector(IJBOwnable.transferOwnershipToProject.selector), abi.encode());
|
|
121
|
-
|
|
122
|
-
// Step 3: Owner claims collection ownership.
|
|
123
|
-
vm.prank(owner);
|
|
124
|
-
ctDeployer.claimCollectionOwnershipOf(hook);
|
|
125
|
-
|
|
126
|
-
// Step 4: After claiming, the hook ownership has been transferred to the project.
|
|
127
|
-
// Verify transferOwnershipToProject was called with the correct projectId.
|
|
128
|
-
// (If it wasn't, the mock would not match and the call would have failed.)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/// @notice After claiming ownership, the hook's owner is resolved via PROJECTS.ownerOf(projectId).
|
|
132
|
-
/// If the project NFT is transferred to a new owner, the new owner becomes the hook's owner.
|
|
133
|
-
function test_postClaim_projectTransfer_newOwnerControlsHook() public {
|
|
134
|
-
// Mock hook.PROJECT_ID().
|
|
135
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(deployedProjectId));
|
|
136
|
-
|
|
137
|
-
// Initially, 'owner' owns the project.
|
|
138
|
-
vm.mockCall(
|
|
139
|
-
address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, deployedProjectId), abi.encode(owner)
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
// Mock transferOwnershipToProject.
|
|
143
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.transferOwnershipToProject.selector), abi.encode());
|
|
144
|
-
|
|
145
|
-
// Owner claims collection ownership.
|
|
146
|
-
vm.prank(owner);
|
|
147
|
-
ctDeployer.claimCollectionOwnershipOf(IJB721TiersHook(hookAddr));
|
|
148
|
-
|
|
149
|
-
// Now simulate the project NFT being transferred to newOwner.
|
|
150
|
-
vm.mockCall(
|
|
151
|
-
address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, deployedProjectId), abi.encode(newOwner)
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
// After the hook is owned by the project, the JBOwnable.owner() resolves to PROJECTS.ownerOf(projectId).
|
|
155
|
-
// Verify that if newOwner holds the project NFT, they are the effective owner.
|
|
156
|
-
// Mock hook.owner() to return newOwner (simulating JBOwnable resolution).
|
|
157
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.owner.selector), abi.encode(newOwner));
|
|
158
|
-
|
|
159
|
-
// Verify: the old owner can no longer claim (since they don't own the project NFT anymore).
|
|
160
|
-
vm.prank(owner);
|
|
161
|
-
vm.expectRevert(
|
|
162
|
-
abi.encodeWithSelector(CTDeployer.CTDeployer_NotOwnerOfProject.selector, deployedProjectId, hookAddr, owner)
|
|
163
|
-
);
|
|
164
|
-
ctDeployer.claimCollectionOwnershipOf(IJB721TiersHook(hookAddr));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/// @notice After claimCollectionOwnership, the deployer's permissions from address(this) are
|
|
168
|
-
/// no longer relevant. The project owner must grant CTPublisher the ADJUST_721_TIERS
|
|
169
|
-
/// permission for mintFrom() to work. Without it, posts would revert.
|
|
170
|
-
///
|
|
171
|
-
/// This test verifies the permission gap: after claim, the hook checks permissions
|
|
172
|
-
/// against the project (not the deployer), so the publisher needs new permissions.
|
|
173
|
-
function test_postClaim_publisherNeedsNewPermissions() public {
|
|
174
|
-
// After claiming, hook.owner() resolves to PROJECTS.ownerOf(projectId) = owner.
|
|
175
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.owner.selector), abi.encode(owner));
|
|
176
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(deployedProjectId));
|
|
177
|
-
|
|
178
|
-
// The publisher's configurePostingCriteriaFor calls _requirePermissionFrom(hook.owner(), ...).
|
|
179
|
-
// After claiming, hook.owner() is the project owner, not the deployer.
|
|
180
|
-
// So the project owner must grant CTPublisher the ADJUST_721_TIERS permission.
|
|
181
|
-
|
|
182
|
-
// Mock permissions: simulate that the project owner has NOT yet granted the publisher permission.
|
|
183
|
-
vm.mockCall(
|
|
184
|
-
address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(false)
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
// The publisher's configurePostingCriteriaFor should revert because the publisher
|
|
188
|
-
// doesn't have permission from the new owner.
|
|
189
|
-
CTAllowedPost[] memory allowedPosts = new CTAllowedPost[](1);
|
|
190
|
-
allowedPosts[0] = CTAllowedPost({
|
|
191
|
-
hook: hookAddr,
|
|
192
|
-
category: 1,
|
|
193
|
-
minimumPrice: 0.01 ether,
|
|
194
|
-
minimumTotalSupply: 1,
|
|
195
|
-
maximumTotalSupply: 100,
|
|
196
|
-
maximumSplitPercent: 0,
|
|
197
|
-
allowedAddresses: new address[](0)
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Expect the publisher to revert due to missing permissions.
|
|
201
|
-
vm.expectRevert(
|
|
202
|
-
abi.encodeWithSelector(
|
|
203
|
-
JBPermissioned.JBPermissioned_Unauthorized.selector,
|
|
204
|
-
owner, // account (hook.owner())
|
|
205
|
-
address(this), // caller (us, calling configurePostingCriteriaFor)
|
|
206
|
-
deployedProjectId,
|
|
207
|
-
JBPermissionIds.ADJUST_721_TIERS
|
|
208
|
-
)
|
|
209
|
-
);
|
|
210
|
-
publisher.configurePostingCriteriaFor(allowedPosts);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/// @notice After granting the publisher new permissions, posting works again.
|
|
214
|
-
function test_postClaim_publisherWorksAfterPermissionGrant() public {
|
|
215
|
-
// After claiming, hook.owner() = owner.
|
|
216
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.owner.selector), abi.encode(owner));
|
|
217
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(deployedProjectId));
|
|
218
|
-
|
|
219
|
-
// The project owner grants CTPublisher the ADJUST_721_TIERS permission.
|
|
220
|
-
// Simulate: hasPermission returns true for publisher calling from owner's context.
|
|
221
|
-
vm.mockCall(
|
|
222
|
-
address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(true)
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
CTAllowedPost[] memory allowedPosts = new CTAllowedPost[](1);
|
|
226
|
-
allowedPosts[0] = CTAllowedPost({
|
|
227
|
-
hook: hookAddr,
|
|
228
|
-
category: 2,
|
|
229
|
-
minimumPrice: 0.05 ether,
|
|
230
|
-
minimumTotalSupply: 1,
|
|
231
|
-
maximumTotalSupply: 50,
|
|
232
|
-
maximumSplitPercent: 0,
|
|
233
|
-
allowedAddresses: new address[](0)
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// This should succeed because the publisher has the correct permission.
|
|
237
|
-
publisher.configurePostingCriteriaFor(allowedPosts);
|
|
238
|
-
|
|
239
|
-
// Verify the allowance was set.
|
|
240
|
-
(uint256 minPrice, uint256 minSupply, uint256 maxSupply,,) = publisher.allowanceFor(hookAddr, 2);
|
|
241
|
-
assertEq(minPrice, 0.05 ether, "minimum price should be configured after re-grant");
|
|
242
|
-
assertEq(minSupply, 1, "minimum supply should be configured");
|
|
243
|
-
assertEq(maxSupply, 50, "maximum supply should be configured");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/// @notice Claiming twice for the same hook should succeed (idempotent — just calls
|
|
247
|
-
/// transferOwnershipToProject again, which the hook handles internally).
|
|
248
|
-
function test_claim_calledTwice_succeeds() public {
|
|
249
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(deployedProjectId));
|
|
250
|
-
vm.mockCall(
|
|
251
|
-
address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, deployedProjectId), abi.encode(owner)
|
|
252
|
-
);
|
|
253
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.transferOwnershipToProject.selector), abi.encode());
|
|
254
|
-
|
|
255
|
-
// First claim.
|
|
256
|
-
vm.prank(owner);
|
|
257
|
-
ctDeployer.claimCollectionOwnershipOf(IJB721TiersHook(hookAddr));
|
|
258
|
-
|
|
259
|
-
// Second claim (should not revert with our mocks).
|
|
260
|
-
vm.prank(owner);
|
|
261
|
-
ctDeployer.claimCollectionOwnershipOf(IJB721TiersHook(hookAddr));
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/// @notice Non-owner cannot claim even after project ownership changes.
|
|
265
|
-
function test_claim_revertsForNonProjectOwner() public {
|
|
266
|
-
vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(deployedProjectId));
|
|
267
|
-
vm.mockCall(
|
|
268
|
-
address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, deployedProjectId), abi.encode(owner)
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
vm.prank(unauthorized);
|
|
272
|
-
vm.expectRevert(
|
|
273
|
-
abi.encodeWithSelector(
|
|
274
|
-
CTDeployer.CTDeployer_NotOwnerOfProject.selector, deployedProjectId, hookAddr, unauthorized
|
|
275
|
-
)
|
|
276
|
-
);
|
|
277
|
-
ctDeployer.claimCollectionOwnershipOf(IJB721TiersHook(hookAddr));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
//*********************************************************************//
|
|
281
|
-
// --- Internal Helpers ---------------------------------------------- //
|
|
282
|
-
//*********************************************************************//
|
|
283
|
-
|
|
284
|
-
function _defaultProjectConfig() internal pure returns (CTProjectConfig memory) {
|
|
285
|
-
return CTProjectConfig({
|
|
286
|
-
terminalConfigurations: new JBTerminalConfig[](0),
|
|
287
|
-
projectUri: "https://croptop.test/",
|
|
288
|
-
allowedPosts: new CTDeployerAllowedPost[](0),
|
|
289
|
-
contractUri: "https://croptop.test/contract",
|
|
290
|
-
name: "TestCrop",
|
|
291
|
-
symbol: "TC",
|
|
292
|
-
salt: bytes32(0)
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function _emptySuckerConfig() internal pure returns (CTSuckerDeploymentConfig memory) {
|
|
297
|
-
return CTSuckerDeploymentConfig({deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32(0)});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function _mockDeployProjectInfra() internal {
|
|
301
|
-
vm.mockCall(address(controller), abi.encodeWithSelector(IJBController.PROJECTS.selector), abi.encode(projects));
|
|
302
|
-
vm.mockCall(address(projects), abi.encodeWithSelector(IJBProjects.count.selector), abi.encode(projectCount));
|
|
303
|
-
vm.mockCall(
|
|
304
|
-
address(hookDeployer),
|
|
305
|
-
abi.encodeWithSelector(IJB721TiersHookDeployer.deployHookFor.selector),
|
|
306
|
-
abi.encode(IJB721TiersHook(hookAddr))
|
|
307
|
-
);
|
|
308
|
-
vm.mockCall(
|
|
309
|
-
address(controller),
|
|
310
|
-
abi.encodeWithSelector(IJBController.launchProjectFor.selector),
|
|
311
|
-
abi.encode(deployedProjectId)
|
|
312
|
-
);
|
|
313
|
-
vm.mockCall(address(projects), abi.encodeWithSelector(IERC721.transferFrom.selector), abi.encode());
|
|
314
|
-
}
|
|
315
|
-
}
|