-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathJBSuckerRegistry.sol
More file actions
268 lines (225 loc) · 13.1 KB
/
Copy pathJBSuckerRegistry.sol
File metadata and controls
268 lines (225 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import {JBPermissioned} from "@bananapus/core/src/abstract/JBPermissioned.sol";
import {IJBDirectory} from "@bananapus/core/src/interfaces/IJBDirectory.sol";
import {IJBController} from "@bananapus/core/src/interfaces/IJBController.sol";
import {IJBPermissions} from "@bananapus/core/src/interfaces/IJBPermissions.sol";
import {IJBProjects} from "@bananapus/core/src/interfaces/IJBProjects.sol";
import {JBRuleset} from "@bananapus/core/src/structs/JBRuleset.sol";
import {JBRulesetMetadata} from "@bananapus/core/src/structs/JBRulesetMetadata.sol";
import {JBPermissionIds} from "@bananapus/permission-ids/src/JBPermissionIds.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IJBSucker} from "./interfaces/IJBSucker.sol";
import {IJBSuckerDeployer} from "./interfaces/IJBSuckerDeployer.sol";
import {IJBSuckerRegistry} from "./interfaces/IJBSuckerRegistry.sol";
import {JBSuckerDeployerConfig} from "./structs/JBSuckerDeployerConfig.sol";
import {JBSuckersPair} from "./structs/JBSuckersPair.sol";
import {JBSuckerState} from "./enums/JBSuckerState.sol";
contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerRegistry {
using EnumerableMap for EnumerableMap.AddressToUintMap;
//*********************************************************************//
// --------------------------- custom errors ------------------------- //
//*********************************************************************//
error JBSuckerRegistry_InvalidDeployer(IJBSuckerDeployer deployer);
error JBSuckerRegistry_RulesetDoesNotAllowAddingSucker(uint256 projectId);
error JBSuckerRegistry_SuckerDoesNotBelongToProject(uint256 projectId, address sucker);
error JBSuckerRegistry_SuckerIsNotDeprecated(address sucker, JBSuckerState suckerState);
//*********************************************************************//
// ------------------------- internal constants ----------------------- //
//*********************************************************************//
/// @notice A constant indicating that this sucker exists and belongs to a specific project.
uint256 internal constant _SUCKER_EXISTS = 1;
//*********************************************************************//
// --------------- public immutable stored properties ---------------- //
//*********************************************************************//
/// @notice The juicebox directory.
IJBDirectory public immutable override DIRECTORY;
/// @notice A contract which mints ERC-721s that represent project ownership and transfers.
IJBProjects public immutable override PROJECTS;
//*********************************************************************//
// --------------------- public stored properties -------------------- //
//*********************************************************************//
/// @notice Tracks whether the specified sucker deployer is approved by this registry.
/// @custom:member deployer The address of the deployer to check.
mapping(address deployer => bool) public override suckerDeployerIsAllowed;
//*********************************************************************//
// --------------------- internal stored properties ------------------- //
//*********************************************************************//
/// @notice Tracks the suckers for the specified project.
mapping(uint256 => EnumerableMap.AddressToUintMap) internal _suckersOf;
//*********************************************************************//
// -------------------------- constructor ---------------------------- //
//*********************************************************************//
/// @param directory The juicebox directory.
/// @param permissions A contract storing permissions.
/// @param initialOwner The initial owner of this contract.
constructor(
IJBDirectory directory,
IJBPermissions permissions,
address initialOwner,
address trusted_forwarder
)
ERC2771Context(trusted_forwarder)
JBPermissioned(permissions)
Ownable(initialOwner)
{
DIRECTORY = directory;
PROJECTS = directory.PROJECTS();
}
//*********************************************************************//
// ------------------------- external views -------------------------- //
//*********************************************************************//
/// @notice Returns true if the specified sucker belongs to the specified project, and was deployed through this
/// registry.
/// @param projectId The ID of the project to check for.
/// @param addr The address of the sucker to check.
/// @return flag A flag indicating if the sucker belongs to the project, and was deployed through this registry.
function isSuckerOf(uint256 projectId, address addr) external view override returns (bool) {
(bool exists, uint256 val) = _suckersOf[projectId].tryGet(addr);
return exists && val == _SUCKER_EXISTS;
}
/// @notice Helper function for retrieving the projects suckers and their metadata.
/// @param projectId The ID of the project to get the suckers of.
/// @return pairs The pairs of suckers and their metadata.
function suckerPairsOf(uint256 projectId) external view override returns (JBSuckersPair[] memory pairs) {
// Get the suckers of the project.
address[] memory suckers = _suckersOf[projectId].keys();
// Initialize the array of pairs.
pairs = new JBSuckersPair[](suckers.length);
// Populate the array of pairs.
for (uint256 i; i < suckers.length; i++) {
// Get the sucker being iterated over.
IJBSucker sucker = IJBSucker(suckers[i]);
// slither-disable-next-line calls-loop
pairs[i] =
JBSuckersPair({local: address(sucker), remote: sucker.peer(), remoteChainId: sucker.peerChainId()});
}
}
/// @notice Gets all of the specified project's suckers which were deployed through this registry.
/// @param projectId The ID of the project to get the suckers of.
/// @return suckers The addresses of the suckers.
function suckersOf(uint256 projectId) external view override returns (address[] memory) {
return _suckersOf[projectId].keys();
}
//*********************************************************************//
// ------------------------ internal views --------------------------- //
//*********************************************************************//
/// @notice The calldata. Preferred to use over `msg.data`.
/// @return calldata The `msg.data` of this call.
function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
/// @notice The message's sender. Preferred to use over `msg.sender`.
/// @return sender The address which sent this call.
function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
return ERC2771Context._msgSender();
}
/// @dev ERC-2771 specifies the context as being a single address (20 bytes).
function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
return ERC2771Context._contextSuffixLength();
}
//*********************************************************************//
// ---------------------- external transactions ---------------------- //
//*********************************************************************//
/// @notice Adds a suckers deployer to the allowlist.
/// @dev Can only be called by this contract's owner (initially project ID 1, or JuiceboxDAO).
/// @param deployer The address of the deployer to add.
function allowSuckerDeployer(address deployer) public override onlyOwner {
suckerDeployerIsAllowed[deployer] = true;
emit SuckerDeployerAllowed({deployer: deployer, caller: _msgSender()});
}
/// @notice Adds multiple suckers deployer to the allowlist.
/// @dev Can only be called by this contract's owner (initially project ID 1, or JuiceboxDAO).
/// @param deployers The address of the deployer to add.
function allowSuckerDeployers(address[] calldata deployers) public override onlyOwner {
// Iterate through the deployers and allow them.
for (uint256 i; i < deployers.length; i++) {
// Get the deployer being iterated over.
address deployer = deployers[i];
// Allow the deployer.
suckerDeployerIsAllowed[deployer] = true;
emit SuckerDeployerAllowed({deployer: deployer, caller: _msgSender()});
}
}
/// @notice Deploy one or more suckers for the specified project.
/// @dev The caller must be the project's owner or have `JBPermissionIds.DEPLOY_SUCKERS` from the project's owner.
/// @param projectId The ID of the project to deploy suckers for.
/// @param salt The salt used to deploy the contract. For the suckers to be peers, this must be the same value on
/// each chain where suckers are deployed.
/// @param configurations The sucker deployer configs to use to deploy the suckers.
/// @return suckers The addresses of the deployed suckers.
function deploySuckersFor(
uint256 projectId,
bytes32 salt,
JBSuckerDeployerConfig[] calldata configurations
)
public
override
returns (address[] memory suckers)
{
_requirePermissionFrom({
account: PROJECTS.ownerOf(projectId),
projectId: projectId,
permissionId: JBPermissionIds.DEPLOY_SUCKERS
});
// Create an array to store the suckers as they are deployed.
suckers = new address[](configurations.length);
// Calculate the salt using the sender's address and the provided `salt`.
// This means that for suckers to be peers, the sender has to be the same on each chain.
salt = keccak256(abi.encode(_msgSender(), salt));
// Iterate through the configurations and deploy the suckers.
for (uint256 i; i < configurations.length; i++) {
// Get the configuration being iterated over.
JBSuckerDeployerConfig memory configuration = configurations[i];
// Make sure the deployer is allowed.
if (!suckerDeployerIsAllowed[address(configuration.deployer)]) {
revert JBSuckerRegistry_InvalidDeployer(configuration.deployer);
}
// Create the sucker.
// slither-disable-next-line reentrancy-event,calls-loop
IJBSucker sucker = configuration.deployer.createForSender({localProjectId: projectId, salt: salt});
suckers[i] = address(sucker);
// Store the sucker as being deployed for this project.
// slither-disable-next-line unused-return
_suckersOf[projectId].set({key: address(sucker), value: _SUCKER_EXISTS});
// Map the tokens for the sucker.
// slither-disable-next-line reentrancy-events,calls-loop
sucker.mapTokens(configuration.mappings);
emit SuckerDeployedFor({
projectId: projectId,
sucker: address(sucker),
configuration: configuration,
caller: _msgSender()
});
}
}
/// @notice Lets anyone remove a deprecated sucker from a project.
/// @param projectId The ID of the project to remove the sucker from.
/// @param sucker The address of the deprecated sucker to remove.
function removeDeprecatedSucker(uint256 projectId, address sucker) public override {
// Sanity check, make sure that the sucker does actually belong to the project.
(bool belongsToProject, uint256 val) = _suckersOf[projectId].tryGet(sucker);
if (!belongsToProject || val != _SUCKER_EXISTS) {
revert JBSuckerRegistry_SuckerDoesNotBelongToProject(projectId, address(sucker));
}
// Check if the sucker is deprecated.
JBSuckerState state = IJBSucker(sucker).state();
if (state != JBSuckerState.DEPRECATED) {
revert JBSuckerRegistry_SuckerIsNotDeprecated(address(sucker), state);
}
// Remove the sucker from the registry.
// slither-disable-next-line unused-return
_suckersOf[projectId].remove(address(sucker));
emit SuckerDeprecated({projectId: projectId, sucker: address(sucker), caller: _msgSender()});
}
/// @notice Removes a sucker deployer from the allowlist.
/// @dev Can only be called by this contract's owner (initially project ID 1, or JuiceboxDAO).
/// @param deployer The address of the deployer to remove.
function removeSuckerDeployer(address deployer) public override onlyOwner {
suckerDeployerIsAllowed[deployer] = false;
emit SuckerDeployerRemoved({deployer: deployer, caller: _msgSender()});
}
}