@bananapus/core-v6 0.0.9 → 0.0.10
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/foundry.toml +0 -1
- package/package.json +2 -2
- package/src/JBChainlinkV3PriceFeed.sol +1 -5
- package/src/JBChainlinkV3SequencerPriceFeed.sol +1 -1
- package/src/JBController.sol +277 -277
- package/src/JBDeadline.sol +1 -1
- package/src/JBDirectory.sol +93 -93
- package/src/JBERC20.sol +43 -39
- package/src/JBFeelessAddresses.sol +12 -12
- package/src/JBFundAccessLimits.sol +82 -82
- package/src/JBMultiTerminal.sol +313 -313
- package/src/JBPermissions.sol +104 -100
- package/src/JBPrices.sol +68 -68
- package/src/JBProjects.sol +31 -31
- package/src/JBRulesets.sol +422 -422
- package/src/JBSplits.sol +116 -116
- package/src/JBTerminalStore.sol +651 -651
- package/src/JBTokens.sol +41 -41
- package/src/interfaces/IJBCashOutTerminal.sol +25 -7
- package/src/interfaces/IJBController.sol +78 -3
- package/src/interfaces/IJBDirectory.sol +25 -0
- package/src/interfaces/IJBFeeTerminal.sol +31 -0
- package/src/interfaces/IJBFeelessAddresses.sol +4 -0
- package/src/interfaces/IJBFundAccessLimits.sol +5 -0
- package/src/interfaces/IJBMigratable.sol +12 -8
- package/src/interfaces/IJBPayoutTerminal.sol +56 -9
- package/src/interfaces/IJBPermissions.sol +14 -7
- package/src/interfaces/IJBPermitTerminal.sol +4 -0
- package/src/interfaces/IJBPrices.sol +6 -0
- package/src/interfaces/IJBProjects.sol +8 -0
- package/src/interfaces/IJBRulesetApprovalHook.sol +1 -1
- package/src/interfaces/IJBRulesetDataHook.sol +23 -23
- package/src/interfaces/IJBRulesets.sol +54 -33
- package/src/interfaces/IJBSplits.sol +6 -0
- package/src/interfaces/IJBTerminal.sol +36 -0
- package/src/interfaces/IJBTerminalStore.sol +63 -63
- package/src/interfaces/IJBToken.sol +5 -5
- package/src/interfaces/IJBTokens.sol +50 -8
- package/test/TestDurationUnderflow.sol +3 -2
package/src/JBRulesets.sol
CHANGED
|
@@ -82,6 +82,193 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
82
82
|
// solhint-disable-next-line no-empty-blocks
|
|
83
83
|
constructor(IJBDirectory directory) JBControlled(directory) {}
|
|
84
84
|
|
|
85
|
+
//*********************************************************************//
|
|
86
|
+
// ---------------------- external transactions ---------------------- //
|
|
87
|
+
//*********************************************************************//
|
|
88
|
+
|
|
89
|
+
/// @notice Queues the upcoming approvable ruleset for the specified project.
|
|
90
|
+
/// @dev Only a project's current controller can queue its rulesets.
|
|
91
|
+
/// @param projectId The ID of the project to queue the ruleset for.
|
|
92
|
+
/// @param duration The number of seconds the ruleset lasts for, after which a new ruleset starts.
|
|
93
|
+
/// - A `duration` of 0 means this ruleset will remain active until the project owner queues a new ruleset. That new
|
|
94
|
+
/// ruleset will start immediately.
|
|
95
|
+
/// - A ruleset with a non-zero `duration` applies until the duration ends – any newly queued rulesets will be
|
|
96
|
+
/// *queued* to take effect afterwards.
|
|
97
|
+
/// - If a duration ends and no new rulesets are queued, the ruleset rolls over to a new ruleset with the same rules
|
|
98
|
+
/// (except for a new `start` timestamp and a cut `weight`).
|
|
99
|
+
/// @param weight A fixed point number with 18 decimals that contracts can use to base arbitrary calculations on.
|
|
100
|
+
/// Payment terminals generally use this to determine how many tokens should be minted when the project is paid.
|
|
101
|
+
/// @param weightCutPercent A fraction (out of `JBConstants.MAX_WEIGHT_CUT_PERCENT`) to reduce the next ruleset's
|
|
102
|
+
/// `weight`
|
|
103
|
+
/// by.
|
|
104
|
+
/// - If a ruleset specifies a non-zero `weight`, the `weightCutPercent` does not apply.
|
|
105
|
+
/// - If the `weightCutPercent` is 0, the `weight` stays the same.
|
|
106
|
+
/// - If the `weightCutPercent` is 10% of `JBConstants.MAX_WEIGHT_CUT_PERCENT`, next ruleset's `weight` will be 90%
|
|
107
|
+
/// of the
|
|
108
|
+
/// current
|
|
109
|
+
/// one.
|
|
110
|
+
/// @param approvalHook A contract which dictates whether a proposed ruleset should be accepted or rejected. It can
|
|
111
|
+
/// be used to constrain a project owner's ability to change ruleset parameters over time.
|
|
112
|
+
/// @param metadata Arbitrary extra data to associate with this ruleset. This metadata is not used by `JBRulesets`.
|
|
113
|
+
/// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
|
|
114
|
+
/// timestamp.
|
|
115
|
+
/// @return The struct of the new ruleset.
|
|
116
|
+
function queueFor(
|
|
117
|
+
uint256 projectId,
|
|
118
|
+
uint256 duration,
|
|
119
|
+
uint256 weight,
|
|
120
|
+
uint256 weightCutPercent,
|
|
121
|
+
IJBRulesetApprovalHook approvalHook,
|
|
122
|
+
uint256 metadata,
|
|
123
|
+
uint256 mustStartAtOrAfter
|
|
124
|
+
)
|
|
125
|
+
external
|
|
126
|
+
override
|
|
127
|
+
onlyControllerOf(projectId)
|
|
128
|
+
returns (JBRuleset memory)
|
|
129
|
+
{
|
|
130
|
+
// Duration must fit in a uint32.
|
|
131
|
+
if (duration > type(uint32).max) revert JBRulesets_InvalidRulesetDuration(duration, type(uint32).max);
|
|
132
|
+
|
|
133
|
+
// Weight cut percent must be less than or equal to 100%.
|
|
134
|
+
if (weightCutPercent > JBConstants.MAX_WEIGHT_CUT_PERCENT) {
|
|
135
|
+
revert JBRulesets_InvalidWeightCutPercent(weightCutPercent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Weight must fit into a uint112.
|
|
139
|
+
if (weight > type(uint112).max) revert JBRulesets_InvalidWeight(weight, type(uint112).max);
|
|
140
|
+
|
|
141
|
+
// If the start date is not set, set it to be the current timestamp.
|
|
142
|
+
if (mustStartAtOrAfter == 0) {
|
|
143
|
+
mustStartAtOrAfter = block.timestamp;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Make sure the min start date fits in a uint48, and that the start date of the following ruleset will also fit
|
|
147
|
+
// within the max.
|
|
148
|
+
if (mustStartAtOrAfter + duration > type(uint48).max) {
|
|
149
|
+
revert JBRulesets_InvalidRulesetEndTime(mustStartAtOrAfter + duration, type(uint48).max);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Approval hook should be a valid contract, supporting the correct interface
|
|
153
|
+
if (approvalHook != IJBRulesetApprovalHook(address(0))) {
|
|
154
|
+
// Revert if there isn't a contract at the address
|
|
155
|
+
if (address(approvalHook).code.length == 0) revert JBRulesets_InvalidRulesetApprovalHook(approvalHook);
|
|
156
|
+
|
|
157
|
+
// Make sure the approval hook supports the expected interface.
|
|
158
|
+
try approvalHook.supportsInterface(type(IJBRulesetApprovalHook).interfaceId) returns (bool doesSupport) {
|
|
159
|
+
if (!doesSupport) revert JBRulesets_InvalidRulesetApprovalHook(approvalHook); // Contract exists at the
|
|
160
|
+
// address but
|
|
161
|
+
// with the
|
|
162
|
+
// wrong interface
|
|
163
|
+
} catch {
|
|
164
|
+
revert JBRulesets_InvalidRulesetApprovalHook(approvalHook); // No ERC165 support
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Get a reference to the latest ruleset's ID.
|
|
169
|
+
uint256 latestId = latestRulesetIdOf[projectId];
|
|
170
|
+
|
|
171
|
+
// The new rulesetId timestamp is now, or an increment from now if the current timestamp is taken.
|
|
172
|
+
uint256 rulesetId = latestId >= block.timestamp ? latestId + 1 : block.timestamp;
|
|
173
|
+
|
|
174
|
+
// Set up the ruleset by configuring intrinsic properties.
|
|
175
|
+
_configureIntrinsicPropertiesFor({
|
|
176
|
+
projectId: projectId, rulesetId: rulesetId, weight: weight, mustStartAtOrAfter: mustStartAtOrAfter
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Efficiently stores the ruleset's user-defined properties.
|
|
180
|
+
// If all user config properties are zero, no need to store anything as the default value will have the same
|
|
181
|
+
// outcome.
|
|
182
|
+
if (approvalHook != IJBRulesetApprovalHook(address(0)) || duration > 0 || weightCutPercent > 0) {
|
|
183
|
+
// approval hook in bits 0-159 bytes.
|
|
184
|
+
uint256 packed = uint160(address(approvalHook));
|
|
185
|
+
|
|
186
|
+
// duration in bits 160-191 bytes.
|
|
187
|
+
packed |= duration << 160;
|
|
188
|
+
|
|
189
|
+
// weightCutPercent in bits 192-223 bytes.
|
|
190
|
+
packed |= weightCutPercent << 192;
|
|
191
|
+
|
|
192
|
+
// Set in storage.
|
|
193
|
+
_packedUserPropertiesOf[projectId][rulesetId] = packed;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Set the metadata if needed.
|
|
197
|
+
if (metadata > 0) _metadataOf[projectId][rulesetId] = metadata;
|
|
198
|
+
|
|
199
|
+
emit RulesetQueued({
|
|
200
|
+
rulesetId: rulesetId,
|
|
201
|
+
projectId: projectId,
|
|
202
|
+
duration: duration,
|
|
203
|
+
weight: weight,
|
|
204
|
+
weightCutPercent: weightCutPercent,
|
|
205
|
+
approvalHook: approvalHook,
|
|
206
|
+
metadata: metadata,
|
|
207
|
+
mustStartAtOrAfter: mustStartAtOrAfter,
|
|
208
|
+
caller: msg.sender
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Return the struct for the new ruleset's ID.
|
|
212
|
+
return _getStructFor({projectId: projectId, rulesetId: rulesetId});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// @notice Cache the value of the ruleset weight for a specific ruleset.
|
|
216
|
+
/// @dev The caller should pass the ruleset ID that `currentOf()` actually uses. When a queued ruleset is rejected
|
|
217
|
+
/// by an approval hook, `currentOf()` falls back to the base ruleset — callers should pass that base ruleset's
|
|
218
|
+
/// ID,
|
|
219
|
+
/// not the rejected latest.
|
|
220
|
+
/// @param projectId The ID of the project having its ruleset weight cached.
|
|
221
|
+
/// @param rulesetId The ID of the ruleset to update the cache for.
|
|
222
|
+
function updateRulesetWeightCache(uint256 projectId, uint256 rulesetId) external override {
|
|
223
|
+
// Get the target ruleset.
|
|
224
|
+
JBRuleset memory targetRuleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
|
|
225
|
+
|
|
226
|
+
// Nothing to cache if the target ruleset doesn't have a duration or a weight cut percent.
|
|
227
|
+
// slither-disable-next-line incorrect-equality
|
|
228
|
+
if (targetRuleset.duration == 0 || targetRuleset.weightCutPercent == 0) return;
|
|
229
|
+
|
|
230
|
+
// Get a reference to the current cache.
|
|
231
|
+
JBRulesetWeightCache storage cache = _weightCacheOf[projectId][targetRuleset.id];
|
|
232
|
+
|
|
233
|
+
// Determine the largest start timestamp the cache can be filled to.
|
|
234
|
+
// Cap the advance to the cache lookup threshold per call to stay within the iteration limit in
|
|
235
|
+
// deriveWeightFrom.
|
|
236
|
+
// Multiple calls are needed to advance the cache for large cycle gaps.
|
|
237
|
+
uint256 maxStart = targetRuleset.start + (cache.weightCutMultiple + _WEIGHT_CUT_MULTIPLE_CACHE_LOOKUP_THRESHOLD)
|
|
238
|
+
* targetRuleset.duration;
|
|
239
|
+
|
|
240
|
+
// Determine the start timestamp to derive a weight from for the cache.
|
|
241
|
+
uint256 start = block.timestamp < maxStart ? block.timestamp : maxStart;
|
|
242
|
+
|
|
243
|
+
// The difference between the start of the latest queued ruleset and the start of the ruleset we're caching the
|
|
244
|
+
// weight of.
|
|
245
|
+
uint256 startDistance = start - targetRuleset.start;
|
|
246
|
+
|
|
247
|
+
// Calculate the weight cut multiple.
|
|
248
|
+
uint168 weightCutMultiple;
|
|
249
|
+
unchecked {
|
|
250
|
+
weightCutMultiple = uint168(startDistance / targetRuleset.duration);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Store the new values.
|
|
254
|
+
cache.weight = uint112(
|
|
255
|
+
deriveWeightFrom({
|
|
256
|
+
projectId: projectId,
|
|
257
|
+
baseRulesetStart: targetRuleset.start,
|
|
258
|
+
baseRulesetDuration: targetRuleset.duration,
|
|
259
|
+
baseRulesetWeight: targetRuleset.weight,
|
|
260
|
+
baseRulesetWeightCutPercent: targetRuleset.weightCutPercent,
|
|
261
|
+
baseRulesetCacheId: targetRuleset.id,
|
|
262
|
+
start: start
|
|
263
|
+
})
|
|
264
|
+
);
|
|
265
|
+
cache.weightCutMultiple = weightCutMultiple;
|
|
266
|
+
|
|
267
|
+
emit WeightCacheUpdated({
|
|
268
|
+
projectId: projectId, weight: cache.weight, weightCutMultiple: weightCutMultiple, caller: msg.sender
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
85
272
|
//*********************************************************************//
|
|
86
273
|
// ------------------------- external views -------------------------- //
|
|
87
274
|
//*********************************************************************//
|
|
@@ -495,51 +682,248 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
495
682
|
}
|
|
496
683
|
|
|
497
684
|
//*********************************************************************//
|
|
498
|
-
//
|
|
685
|
+
// ---------------------- internal transactions ---------------------- //
|
|
499
686
|
//*********************************************************************//
|
|
500
687
|
|
|
501
|
-
/// @notice
|
|
502
|
-
/// @param projectId The ID of the project the ruleset
|
|
503
|
-
/// @param
|
|
504
|
-
/// @
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
688
|
+
/// @notice Updates the latest ruleset for this project if it exists. If there is no ruleset, initializes one.
|
|
689
|
+
/// @param projectId The ID of the project to update the latest ruleset for.
|
|
690
|
+
/// @param rulesetId The timestamp of when the ruleset was queued.
|
|
691
|
+
/// @param weight The weight to store in the queued ruleset.
|
|
692
|
+
/// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
|
|
693
|
+
/// timestamp.
|
|
694
|
+
function _configureIntrinsicPropertiesFor(
|
|
695
|
+
uint256 projectId,
|
|
696
|
+
uint256 rulesetId,
|
|
697
|
+
uint256 weight,
|
|
698
|
+
uint256 mustStartAtOrAfter
|
|
699
|
+
)
|
|
700
|
+
internal
|
|
701
|
+
{
|
|
702
|
+
// Keep a reference to the project's latest ruleset's ID.
|
|
703
|
+
uint256 latestId = latestRulesetIdOf[projectId];
|
|
512
704
|
|
|
513
|
-
// If
|
|
514
|
-
|
|
515
|
-
|
|
705
|
+
// If the project doesn't have a ruleset yet, initialize one.
|
|
706
|
+
// slither-disable-next-line incorrect-equality
|
|
707
|
+
if (latestId == 0) {
|
|
708
|
+
// Use an empty ruleset as the base.
|
|
709
|
+
return _initializeRulesetFor({
|
|
710
|
+
projectId: projectId,
|
|
711
|
+
baseRuleset: _getStructFor({projectId: 0, rulesetId: 0}),
|
|
712
|
+
rulesetId: rulesetId,
|
|
713
|
+
mustStartAtOrAfter: mustStartAtOrAfter,
|
|
714
|
+
weight: weight
|
|
715
|
+
});
|
|
516
716
|
}
|
|
517
717
|
|
|
518
|
-
//
|
|
519
|
-
|
|
520
|
-
// Note: A malicious hook that consumes all gas (e.g. infinite loop) could still DoS via gas exhaustion.
|
|
521
|
-
// This is accepted risk since the project owner chose their own approval hook.
|
|
522
|
-
// slither-disable-next-line calls-loop
|
|
523
|
-
try approvalHookRuleset.approvalHook.approvalStatusOf({projectId: projectId, ruleset: ruleset}) returns (
|
|
524
|
-
JBApprovalStatus status
|
|
525
|
-
) {
|
|
526
|
-
return status;
|
|
527
|
-
} catch {
|
|
528
|
-
return JBApprovalStatus.Failed;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
718
|
+
// Get a reference to the latest ruleset's struct.
|
|
719
|
+
JBRuleset memory baseRuleset = _getStructFor({projectId: projectId, rulesetId: latestId});
|
|
531
720
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
/// @dev A value of 0 is returned if no ruleset was found.
|
|
535
|
-
/// @dev Assumes the project has a latest ruleset.
|
|
536
|
-
/// @param projectId The ID of the project to check for a currently approvable ruleset.
|
|
537
|
-
/// @return The ID of a currently approvable ruleset if one exists, or 0 if one doesn't exist.
|
|
538
|
-
function _currentlyApprovableRulesetIdOf(uint256 projectId) internal view returns (uint256) {
|
|
539
|
-
// Get a reference to the project's latest ruleset.
|
|
540
|
-
uint256 rulesetId = latestRulesetIdOf[projectId];
|
|
721
|
+
// Get a reference to the approval status.
|
|
722
|
+
JBApprovalStatus approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: baseRuleset});
|
|
541
723
|
|
|
542
|
-
//
|
|
724
|
+
// If the base ruleset has started but wasn't approved if a approval hook exists
|
|
725
|
+
// OR it hasn't started but is currently approved
|
|
726
|
+
// OR it hasn't started but it is likely to be approved and takes place before the proposed one,
|
|
727
|
+
// set the struct to be the ruleset it's based on, which carries the latest approved ruleset.
|
|
728
|
+
if (
|
|
729
|
+
(block.timestamp >= baseRuleset.start
|
|
730
|
+
&& approvalStatus != JBApprovalStatus.Approved
|
|
731
|
+
&& approvalStatus != JBApprovalStatus.Empty)
|
|
732
|
+
|| (block.timestamp < baseRuleset.start
|
|
733
|
+
&& mustStartAtOrAfter < baseRuleset.start + baseRuleset.duration
|
|
734
|
+
&& approvalStatus != JBApprovalStatus.Approved)
|
|
735
|
+
|| (block.timestamp < baseRuleset.start
|
|
736
|
+
&& mustStartAtOrAfter >= baseRuleset.start + baseRuleset.duration
|
|
737
|
+
&& approvalStatus != JBApprovalStatus.Approved
|
|
738
|
+
&& approvalStatus != JBApprovalStatus.ApprovalExpected
|
|
739
|
+
&& approvalStatus != JBApprovalStatus.Empty)
|
|
740
|
+
) {
|
|
741
|
+
baseRuleset = _getStructFor({projectId: projectId, rulesetId: baseRuleset.basedOnId});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Make sure the ruleset starts after the base ruleset.
|
|
745
|
+
if (baseRuleset.start > mustStartAtOrAfter) mustStartAtOrAfter = baseRuleset.start;
|
|
746
|
+
|
|
747
|
+
// The time when the duration of the base ruleset's approval hook has finished.
|
|
748
|
+
// If the provided ruleset has no approval hook, return 0 (no constraint on start time).
|
|
749
|
+
uint256 timestampAfterApprovalHook;
|
|
750
|
+
if (baseRuleset.approvalHook != IJBRulesetApprovalHook(address(0))) {
|
|
751
|
+
try baseRuleset.approvalHook.DURATION() returns (uint256 duration) {
|
|
752
|
+
timestampAfterApprovalHook = rulesetId + duration;
|
|
753
|
+
} catch {
|
|
754
|
+
// If DURATION() reverts, treat as no approval hook constraint.
|
|
755
|
+
timestampAfterApprovalHook = 0;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
_initializeRulesetFor({
|
|
760
|
+
projectId: projectId,
|
|
761
|
+
baseRuleset: baseRuleset,
|
|
762
|
+
rulesetId: rulesetId,
|
|
763
|
+
// Can only start after the approval hook.
|
|
764
|
+
mustStartAtOrAfter: timestampAfterApprovalHook > mustStartAtOrAfter
|
|
765
|
+
? timestampAfterApprovalHook
|
|
766
|
+
: mustStartAtOrAfter,
|
|
767
|
+
weight: weight
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/// @notice Initializes a ruleset with the specified properties.
|
|
772
|
+
/// @param projectId The ID of the project to initialize the ruleset for.
|
|
773
|
+
/// @param baseRuleset The ruleset struct to base the newly initialized one on.
|
|
774
|
+
/// @param rulesetId The `rulesetId` for the ruleset being initialized.
|
|
775
|
+
/// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
|
|
776
|
+
/// timestamp.
|
|
777
|
+
/// @param weight The weight to give the newly initialized ruleset.
|
|
778
|
+
function _initializeRulesetFor(
|
|
779
|
+
uint256 projectId,
|
|
780
|
+
JBRuleset memory baseRuleset,
|
|
781
|
+
uint256 rulesetId,
|
|
782
|
+
uint256 mustStartAtOrAfter,
|
|
783
|
+
uint256 weight
|
|
784
|
+
)
|
|
785
|
+
internal
|
|
786
|
+
{
|
|
787
|
+
// If there is no base, initialize a first ruleset.
|
|
788
|
+
// slither-disable-next-line incorrect-equality
|
|
789
|
+
if (baseRuleset.cycleNumber == 0) {
|
|
790
|
+
// Set fresh intrinsic properties.
|
|
791
|
+
_packAndStoreIntrinsicPropertiesOf({
|
|
792
|
+
rulesetId: rulesetId,
|
|
793
|
+
projectId: projectId,
|
|
794
|
+
rulesetCycleNumber: 1,
|
|
795
|
+
weight: weight,
|
|
796
|
+
basedOnId: baseRuleset.id,
|
|
797
|
+
start: mustStartAtOrAfter
|
|
798
|
+
});
|
|
799
|
+
} else {
|
|
800
|
+
// Derive the correct next start time from the base.
|
|
801
|
+
uint256 start = deriveStartFrom({
|
|
802
|
+
baseRulesetStart: baseRuleset.start,
|
|
803
|
+
baseRulesetDuration: baseRuleset.duration,
|
|
804
|
+
mustStartAtOrAfter: mustStartAtOrAfter
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// A weight of 1 is a special case that represents inheriting the cut weight of the previous
|
|
808
|
+
// ruleset.
|
|
809
|
+
weight = weight == 1
|
|
810
|
+
? deriveWeightFrom({
|
|
811
|
+
projectId: projectId,
|
|
812
|
+
baseRulesetStart: baseRuleset.start,
|
|
813
|
+
baseRulesetDuration: baseRuleset.duration,
|
|
814
|
+
baseRulesetWeight: baseRuleset.weight,
|
|
815
|
+
baseRulesetWeightCutPercent: baseRuleset.weightCutPercent,
|
|
816
|
+
baseRulesetCacheId: baseRuleset.id,
|
|
817
|
+
start: start
|
|
818
|
+
})
|
|
819
|
+
: weight;
|
|
820
|
+
|
|
821
|
+
// Derive the correct ruleset cycle number.
|
|
822
|
+
uint256 rulesetCycleNumber = deriveCycleNumberFrom({
|
|
823
|
+
baseRulesetCycleNumber: baseRuleset.cycleNumber,
|
|
824
|
+
baseRulesetStart: baseRuleset.start,
|
|
825
|
+
baseRulesetDuration: baseRuleset.duration,
|
|
826
|
+
start: start
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Update the intrinsic properties.
|
|
830
|
+
_packAndStoreIntrinsicPropertiesOf({
|
|
831
|
+
rulesetId: rulesetId,
|
|
832
|
+
projectId: projectId,
|
|
833
|
+
rulesetCycleNumber: rulesetCycleNumber,
|
|
834
|
+
weight: weight,
|
|
835
|
+
basedOnId: baseRuleset.id,
|
|
836
|
+
start: start
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Set the project's latest ruleset configuration.
|
|
841
|
+
latestRulesetIdOf[projectId] = rulesetId;
|
|
842
|
+
|
|
843
|
+
emit RulesetInitialized({
|
|
844
|
+
rulesetId: rulesetId, projectId: projectId, basedOnId: baseRuleset.id, caller: msg.sender
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/// @notice Efficiently stores the provided intrinsic properties of a ruleset.
|
|
849
|
+
/// @param rulesetId The `rulesetId` of the ruleset to pack and store for.
|
|
850
|
+
/// @param projectId The ID of the project the ruleset belongs to.
|
|
851
|
+
/// @param rulesetCycleNumber The cycle number of the ruleset.
|
|
852
|
+
/// @param weight The weight of the ruleset.
|
|
853
|
+
/// @param basedOnId The `rulesetId` of the ruleset this ruleset was based on.
|
|
854
|
+
/// @param start The start time of this ruleset.
|
|
855
|
+
function _packAndStoreIntrinsicPropertiesOf(
|
|
856
|
+
uint256 rulesetId,
|
|
857
|
+
uint256 projectId,
|
|
858
|
+
uint256 rulesetCycleNumber,
|
|
859
|
+
uint256 weight,
|
|
860
|
+
uint256 basedOnId,
|
|
861
|
+
uint256 start
|
|
862
|
+
)
|
|
863
|
+
internal
|
|
864
|
+
{
|
|
865
|
+
// `weight` in bits 0-111.
|
|
866
|
+
uint256 packed = weight;
|
|
867
|
+
|
|
868
|
+
// `basedOnId` in bits 112-159.
|
|
869
|
+
packed |= basedOnId << 112;
|
|
870
|
+
|
|
871
|
+
// `start` in bits 160-207.
|
|
872
|
+
packed |= start << 160;
|
|
873
|
+
|
|
874
|
+
// cycle number in bits 208-255.
|
|
875
|
+
packed |= rulesetCycleNumber << 208;
|
|
876
|
+
|
|
877
|
+
// Store the packed value.
|
|
878
|
+
_packedIntrinsicPropertiesOf[projectId][rulesetId] = packed;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
//*********************************************************************//
|
|
882
|
+
// -------------------------- internal views ------------------------- //
|
|
883
|
+
//*********************************************************************//
|
|
884
|
+
|
|
885
|
+
/// @notice The approval status of a given ruleset for a given project ID.
|
|
886
|
+
/// @param projectId The ID of the project the ruleset belongs to.
|
|
887
|
+
/// @param ruleset The ruleset to get the approval status of.
|
|
888
|
+
/// @return The approval status of the project.
|
|
889
|
+
function _approvalStatusOf(uint256 projectId, JBRuleset memory ruleset) internal view returns (JBApprovalStatus) {
|
|
890
|
+
// If there is no ruleset ID to check the approval hook of, the approval hook is empty.
|
|
891
|
+
// slither-disable-next-line incorrect-equality
|
|
892
|
+
if (ruleset.basedOnId == 0) return JBApprovalStatus.Empty;
|
|
893
|
+
|
|
894
|
+
// Get the struct of the ruleset with the approval hook.
|
|
895
|
+
JBRuleset memory approvalHookRuleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
|
|
896
|
+
|
|
897
|
+
// If there is no approval hook, it's considered empty.
|
|
898
|
+
if (approvalHookRuleset.approvalHook == IJBRulesetApprovalHook(address(0))) {
|
|
899
|
+
return JBApprovalStatus.Empty;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Return the approval hook's approval status.
|
|
903
|
+
// Wrap in try/catch to prevent a reverting approval hook from permanently freezing the project.
|
|
904
|
+
// Note: A malicious hook that consumes all gas (e.g. infinite loop) could still DoS via gas exhaustion.
|
|
905
|
+
// This is accepted risk since the project owner chose their own approval hook.
|
|
906
|
+
// slither-disable-next-line calls-loop
|
|
907
|
+
try approvalHookRuleset.approvalHook.approvalStatusOf({projectId: projectId, ruleset: ruleset}) returns (
|
|
908
|
+
JBApprovalStatus status
|
|
909
|
+
) {
|
|
910
|
+
return status;
|
|
911
|
+
} catch {
|
|
912
|
+
return JBApprovalStatus.Failed;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/// @notice The ID of the ruleset which has started and hasn't expired yet, whether or not it has been approved, for
|
|
917
|
+
/// a given project. If approved, this is the active ruleset.
|
|
918
|
+
/// @dev A value of 0 is returned if no ruleset was found.
|
|
919
|
+
/// @dev Assumes the project has a latest ruleset.
|
|
920
|
+
/// @param projectId The ID of the project to check for a currently approvable ruleset.
|
|
921
|
+
/// @return The ID of a currently approvable ruleset if one exists, or 0 if one doesn't exist.
|
|
922
|
+
function _currentlyApprovableRulesetIdOf(uint256 projectId) internal view returns (uint256) {
|
|
923
|
+
// Get a reference to the project's latest ruleset.
|
|
924
|
+
uint256 rulesetId = latestRulesetIdOf[projectId];
|
|
925
|
+
|
|
926
|
+
// Get the struct for the latest ruleset.
|
|
543
927
|
JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
|
|
544
928
|
|
|
545
929
|
// Loop through all most recently queued rulesets until an approvable one is found, or we've proven one can't
|
|
@@ -709,388 +1093,4 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
709
1093
|
return 0;
|
|
710
1094
|
}
|
|
711
1095
|
}
|
|
712
|
-
|
|
713
|
-
//*********************************************************************//
|
|
714
|
-
// ---------------------- external transactions ---------------------- //
|
|
715
|
-
//*********************************************************************//
|
|
716
|
-
|
|
717
|
-
/// @notice Queues the upcoming approvable ruleset for the specified project.
|
|
718
|
-
/// @dev Only a project's current controller can queue its rulesets.
|
|
719
|
-
/// @param projectId The ID of the project to queue the ruleset for.
|
|
720
|
-
/// @param duration The number of seconds the ruleset lasts for, after which a new ruleset starts.
|
|
721
|
-
/// - A `duration` of 0 means this ruleset will remain active until the project owner queues a new ruleset. That new
|
|
722
|
-
/// ruleset will start immediately.
|
|
723
|
-
/// - A ruleset with a non-zero `duration` applies until the duration ends – any newly queued rulesets will be
|
|
724
|
-
/// *queued* to take effect afterwards.
|
|
725
|
-
/// - If a duration ends and no new rulesets are queued, the ruleset rolls over to a new ruleset with the same rules
|
|
726
|
-
/// (except for a new `start` timestamp and a cut `weight`).
|
|
727
|
-
/// @param weight A fixed point number with 18 decimals that contracts can use to base arbitrary calculations on.
|
|
728
|
-
/// Payment terminals generally use this to determine how many tokens should be minted when the project is paid.
|
|
729
|
-
/// @param weightCutPercent A fraction (out of `JBConstants.MAX_WEIGHT_CUT_PERCENT`) to reduce the next ruleset's
|
|
730
|
-
/// `weight`
|
|
731
|
-
/// by.
|
|
732
|
-
/// - If a ruleset specifies a non-zero `weight`, the `weightCutPercent` does not apply.
|
|
733
|
-
/// - If the `weightCutPercent` is 0, the `weight` stays the same.
|
|
734
|
-
/// - If the `weightCutPercent` is 10% of `JBConstants.MAX_WEIGHT_CUT_PERCENT`, next ruleset's `weight` will be 90%
|
|
735
|
-
/// of the
|
|
736
|
-
/// current
|
|
737
|
-
/// one.
|
|
738
|
-
/// @param approvalHook A contract which dictates whether a proposed ruleset should be accepted or rejected. It can
|
|
739
|
-
/// be used to constrain a project owner's ability to change ruleset parameters over time.
|
|
740
|
-
/// @param metadata Arbitrary extra data to associate with this ruleset. This metadata is not used by `JBRulesets`.
|
|
741
|
-
/// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
|
|
742
|
-
/// timestamp.
|
|
743
|
-
/// @return The struct of the new ruleset.
|
|
744
|
-
function queueFor(
|
|
745
|
-
uint256 projectId,
|
|
746
|
-
uint256 duration,
|
|
747
|
-
uint256 weight,
|
|
748
|
-
uint256 weightCutPercent,
|
|
749
|
-
IJBRulesetApprovalHook approvalHook,
|
|
750
|
-
uint256 metadata,
|
|
751
|
-
uint256 mustStartAtOrAfter
|
|
752
|
-
)
|
|
753
|
-
external
|
|
754
|
-
override
|
|
755
|
-
onlyControllerOf(projectId)
|
|
756
|
-
returns (JBRuleset memory)
|
|
757
|
-
{
|
|
758
|
-
// Duration must fit in a uint32.
|
|
759
|
-
if (duration > type(uint32).max) revert JBRulesets_InvalidRulesetDuration(duration, type(uint32).max);
|
|
760
|
-
|
|
761
|
-
// Weight cut percent must be less than or equal to 100%.
|
|
762
|
-
if (weightCutPercent > JBConstants.MAX_WEIGHT_CUT_PERCENT) {
|
|
763
|
-
revert JBRulesets_InvalidWeightCutPercent(weightCutPercent);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// Weight must fit into a uint112.
|
|
767
|
-
if (weight > type(uint112).max) revert JBRulesets_InvalidWeight(weight, type(uint112).max);
|
|
768
|
-
|
|
769
|
-
// If the start date is not set, set it to be the current timestamp.
|
|
770
|
-
if (mustStartAtOrAfter == 0) {
|
|
771
|
-
mustStartAtOrAfter = block.timestamp;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Make sure the min start date fits in a uint48, and that the start date of the following ruleset will also fit
|
|
775
|
-
// within the max.
|
|
776
|
-
if (mustStartAtOrAfter + duration > type(uint48).max) {
|
|
777
|
-
revert JBRulesets_InvalidRulesetEndTime(mustStartAtOrAfter + duration, type(uint48).max);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// Approval hook should be a valid contract, supporting the correct interface
|
|
781
|
-
if (approvalHook != IJBRulesetApprovalHook(address(0))) {
|
|
782
|
-
// Revert if there isn't a contract at the address
|
|
783
|
-
if (address(approvalHook).code.length == 0) revert JBRulesets_InvalidRulesetApprovalHook(approvalHook);
|
|
784
|
-
|
|
785
|
-
// Make sure the approval hook supports the expected interface.
|
|
786
|
-
try approvalHook.supportsInterface(type(IJBRulesetApprovalHook).interfaceId) returns (bool doesSupport) {
|
|
787
|
-
if (!doesSupport) revert JBRulesets_InvalidRulesetApprovalHook(approvalHook); // Contract exists at the
|
|
788
|
-
// address but
|
|
789
|
-
// with the
|
|
790
|
-
// wrong interface
|
|
791
|
-
} catch {
|
|
792
|
-
revert JBRulesets_InvalidRulesetApprovalHook(approvalHook); // No ERC165 support
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Get a reference to the latest ruleset's ID.
|
|
797
|
-
uint256 latestId = latestRulesetIdOf[projectId];
|
|
798
|
-
|
|
799
|
-
// The new rulesetId timestamp is now, or an increment from now if the current timestamp is taken.
|
|
800
|
-
uint256 rulesetId = latestId >= block.timestamp ? latestId + 1 : block.timestamp;
|
|
801
|
-
|
|
802
|
-
// Set up the ruleset by configuring intrinsic properties.
|
|
803
|
-
_configureIntrinsicPropertiesFor({
|
|
804
|
-
projectId: projectId, rulesetId: rulesetId, weight: weight, mustStartAtOrAfter: mustStartAtOrAfter
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
// Efficiently stores the ruleset's user-defined properties.
|
|
808
|
-
// If all user config properties are zero, no need to store anything as the default value will have the same
|
|
809
|
-
// outcome.
|
|
810
|
-
if (approvalHook != IJBRulesetApprovalHook(address(0)) || duration > 0 || weightCutPercent > 0) {
|
|
811
|
-
// approval hook in bits 0-159 bytes.
|
|
812
|
-
uint256 packed = uint160(address(approvalHook));
|
|
813
|
-
|
|
814
|
-
// duration in bits 160-191 bytes.
|
|
815
|
-
packed |= duration << 160;
|
|
816
|
-
|
|
817
|
-
// weightCutPercent in bits 192-223 bytes.
|
|
818
|
-
packed |= weightCutPercent << 192;
|
|
819
|
-
|
|
820
|
-
// Set in storage.
|
|
821
|
-
_packedUserPropertiesOf[projectId][rulesetId] = packed;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Set the metadata if needed.
|
|
825
|
-
if (metadata > 0) _metadataOf[projectId][rulesetId] = metadata;
|
|
826
|
-
|
|
827
|
-
emit RulesetQueued({
|
|
828
|
-
rulesetId: rulesetId,
|
|
829
|
-
projectId: projectId,
|
|
830
|
-
duration: duration,
|
|
831
|
-
weight: weight,
|
|
832
|
-
weightCutPercent: weightCutPercent,
|
|
833
|
-
approvalHook: approvalHook,
|
|
834
|
-
metadata: metadata,
|
|
835
|
-
mustStartAtOrAfter: mustStartAtOrAfter,
|
|
836
|
-
caller: msg.sender
|
|
837
|
-
});
|
|
838
|
-
|
|
839
|
-
// Return the struct for the new ruleset's ID.
|
|
840
|
-
return _getStructFor({projectId: projectId, rulesetId: rulesetId});
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
/// @notice Cache the value of the ruleset weight for a specific ruleset.
|
|
844
|
-
/// @dev The caller should pass the ruleset ID that `currentOf()` actually uses. When a queued ruleset is rejected
|
|
845
|
-
/// by an approval hook, `currentOf()` falls back to the base ruleset — callers should pass that base ruleset's
|
|
846
|
-
/// ID,
|
|
847
|
-
/// not the rejected latest.
|
|
848
|
-
/// @param projectId The ID of the project having its ruleset weight cached.
|
|
849
|
-
/// @param rulesetId The ID of the ruleset to update the cache for.
|
|
850
|
-
function updateRulesetWeightCache(uint256 projectId, uint256 rulesetId) external override {
|
|
851
|
-
// Get the target ruleset.
|
|
852
|
-
JBRuleset memory targetRuleset = _getStructFor({projectId: projectId, rulesetId: rulesetId});
|
|
853
|
-
|
|
854
|
-
// Nothing to cache if the target ruleset doesn't have a duration or a weight cut percent.
|
|
855
|
-
// slither-disable-next-line incorrect-equality
|
|
856
|
-
if (targetRuleset.duration == 0 || targetRuleset.weightCutPercent == 0) return;
|
|
857
|
-
|
|
858
|
-
// Get a reference to the current cache.
|
|
859
|
-
JBRulesetWeightCache storage cache = _weightCacheOf[projectId][targetRuleset.id];
|
|
860
|
-
|
|
861
|
-
// Determine the largest start timestamp the cache can be filled to.
|
|
862
|
-
// Cap the advance to the cache lookup threshold per call to stay within the iteration limit in
|
|
863
|
-
// deriveWeightFrom.
|
|
864
|
-
// Multiple calls are needed to advance the cache for large cycle gaps.
|
|
865
|
-
uint256 maxStart = targetRuleset.start + (cache.weightCutMultiple + _WEIGHT_CUT_MULTIPLE_CACHE_LOOKUP_THRESHOLD)
|
|
866
|
-
* targetRuleset.duration;
|
|
867
|
-
|
|
868
|
-
// Determine the start timestamp to derive a weight from for the cache.
|
|
869
|
-
uint256 start = block.timestamp < maxStart ? block.timestamp : maxStart;
|
|
870
|
-
|
|
871
|
-
// The difference between the start of the latest queued ruleset and the start of the ruleset we're caching the
|
|
872
|
-
// weight of.
|
|
873
|
-
uint256 startDistance = start - targetRuleset.start;
|
|
874
|
-
|
|
875
|
-
// Calculate the weight cut multiple.
|
|
876
|
-
uint168 weightCutMultiple;
|
|
877
|
-
unchecked {
|
|
878
|
-
weightCutMultiple = uint168(startDistance / targetRuleset.duration);
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// Store the new values.
|
|
882
|
-
cache.weight = uint112(
|
|
883
|
-
deriveWeightFrom({
|
|
884
|
-
projectId: projectId,
|
|
885
|
-
baseRulesetStart: targetRuleset.start,
|
|
886
|
-
baseRulesetDuration: targetRuleset.duration,
|
|
887
|
-
baseRulesetWeight: targetRuleset.weight,
|
|
888
|
-
baseRulesetWeightCutPercent: targetRuleset.weightCutPercent,
|
|
889
|
-
baseRulesetCacheId: targetRuleset.id,
|
|
890
|
-
start: start
|
|
891
|
-
})
|
|
892
|
-
);
|
|
893
|
-
cache.weightCutMultiple = weightCutMultiple;
|
|
894
|
-
|
|
895
|
-
emit WeightCacheUpdated({
|
|
896
|
-
projectId: projectId, weight: cache.weight, weightCutMultiple: weightCutMultiple, caller: msg.sender
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
//*********************************************************************//
|
|
901
|
-
// ------------------------ internal functions ----------------------- //
|
|
902
|
-
//*********************************************************************//
|
|
903
|
-
|
|
904
|
-
/// @notice Updates the latest ruleset for this project if it exists. If there is no ruleset, initializes one.
|
|
905
|
-
/// @param projectId The ID of the project to update the latest ruleset for.
|
|
906
|
-
/// @param rulesetId The timestamp of when the ruleset was queued.
|
|
907
|
-
/// @param weight The weight to store in the queued ruleset.
|
|
908
|
-
/// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
|
|
909
|
-
/// timestamp.
|
|
910
|
-
function _configureIntrinsicPropertiesFor(
|
|
911
|
-
uint256 projectId,
|
|
912
|
-
uint256 rulesetId,
|
|
913
|
-
uint256 weight,
|
|
914
|
-
uint256 mustStartAtOrAfter
|
|
915
|
-
)
|
|
916
|
-
internal
|
|
917
|
-
{
|
|
918
|
-
// Keep a reference to the project's latest ruleset's ID.
|
|
919
|
-
uint256 latestId = latestRulesetIdOf[projectId];
|
|
920
|
-
|
|
921
|
-
// If the project doesn't have a ruleset yet, initialize one.
|
|
922
|
-
// slither-disable-next-line incorrect-equality
|
|
923
|
-
if (latestId == 0) {
|
|
924
|
-
// Use an empty ruleset as the base.
|
|
925
|
-
return _initializeRulesetFor({
|
|
926
|
-
projectId: projectId,
|
|
927
|
-
baseRuleset: _getStructFor({projectId: 0, rulesetId: 0}),
|
|
928
|
-
rulesetId: rulesetId,
|
|
929
|
-
mustStartAtOrAfter: mustStartAtOrAfter,
|
|
930
|
-
weight: weight
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// Get a reference to the latest ruleset's struct.
|
|
935
|
-
JBRuleset memory baseRuleset = _getStructFor({projectId: projectId, rulesetId: latestId});
|
|
936
|
-
|
|
937
|
-
// Get a reference to the approval status.
|
|
938
|
-
JBApprovalStatus approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: baseRuleset});
|
|
939
|
-
|
|
940
|
-
// If the base ruleset has started but wasn't approved if a approval hook exists
|
|
941
|
-
// OR it hasn't started but is currently approved
|
|
942
|
-
// OR it hasn't started but it is likely to be approved and takes place before the proposed one,
|
|
943
|
-
// set the struct to be the ruleset it's based on, which carries the latest approved ruleset.
|
|
944
|
-
if (
|
|
945
|
-
(block.timestamp >= baseRuleset.start
|
|
946
|
-
&& approvalStatus != JBApprovalStatus.Approved
|
|
947
|
-
&& approvalStatus != JBApprovalStatus.Empty)
|
|
948
|
-
|| (block.timestamp < baseRuleset.start
|
|
949
|
-
&& mustStartAtOrAfter < baseRuleset.start + baseRuleset.duration
|
|
950
|
-
&& approvalStatus != JBApprovalStatus.Approved)
|
|
951
|
-
|| (block.timestamp < baseRuleset.start
|
|
952
|
-
&& mustStartAtOrAfter >= baseRuleset.start + baseRuleset.duration
|
|
953
|
-
&& approvalStatus != JBApprovalStatus.Approved
|
|
954
|
-
&& approvalStatus != JBApprovalStatus.ApprovalExpected
|
|
955
|
-
&& approvalStatus != JBApprovalStatus.Empty)
|
|
956
|
-
) {
|
|
957
|
-
baseRuleset = _getStructFor({projectId: projectId, rulesetId: baseRuleset.basedOnId});
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// Make sure the ruleset starts after the base ruleset.
|
|
961
|
-
if (baseRuleset.start > mustStartAtOrAfter) mustStartAtOrAfter = baseRuleset.start;
|
|
962
|
-
|
|
963
|
-
// The time when the duration of the base ruleset's approval hook has finished.
|
|
964
|
-
// If the provided ruleset has no approval hook, return 0 (no constraint on start time).
|
|
965
|
-
uint256 timestampAfterApprovalHook;
|
|
966
|
-
if (baseRuleset.approvalHook != IJBRulesetApprovalHook(address(0))) {
|
|
967
|
-
try baseRuleset.approvalHook.DURATION() returns (uint256 duration) {
|
|
968
|
-
timestampAfterApprovalHook = rulesetId + duration;
|
|
969
|
-
} catch {
|
|
970
|
-
// If DURATION() reverts, treat as no approval hook constraint.
|
|
971
|
-
timestampAfterApprovalHook = 0;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
_initializeRulesetFor({
|
|
976
|
-
projectId: projectId,
|
|
977
|
-
baseRuleset: baseRuleset,
|
|
978
|
-
rulesetId: rulesetId,
|
|
979
|
-
// Can only start after the approval hook.
|
|
980
|
-
mustStartAtOrAfter: timestampAfterApprovalHook > mustStartAtOrAfter
|
|
981
|
-
? timestampAfterApprovalHook
|
|
982
|
-
: mustStartAtOrAfter,
|
|
983
|
-
weight: weight
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
/// @notice Initializes a ruleset with the specified properties.
|
|
988
|
-
/// @param projectId The ID of the project to initialize the ruleset for.
|
|
989
|
-
/// @param baseRuleset The ruleset struct to base the newly initialized one on.
|
|
990
|
-
/// @param rulesetId The `rulesetId` for the ruleset being initialized.
|
|
991
|
-
/// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
|
|
992
|
-
/// timestamp.
|
|
993
|
-
/// @param weight The weight to give the newly initialized ruleset.
|
|
994
|
-
function _initializeRulesetFor(
|
|
995
|
-
uint256 projectId,
|
|
996
|
-
JBRuleset memory baseRuleset,
|
|
997
|
-
uint256 rulesetId,
|
|
998
|
-
uint256 mustStartAtOrAfter,
|
|
999
|
-
uint256 weight
|
|
1000
|
-
)
|
|
1001
|
-
internal
|
|
1002
|
-
{
|
|
1003
|
-
// If there is no base, initialize a first ruleset.
|
|
1004
|
-
// slither-disable-next-line incorrect-equality
|
|
1005
|
-
if (baseRuleset.cycleNumber == 0) {
|
|
1006
|
-
// Set fresh intrinsic properties.
|
|
1007
|
-
_packAndStoreIntrinsicPropertiesOf({
|
|
1008
|
-
rulesetId: rulesetId,
|
|
1009
|
-
projectId: projectId,
|
|
1010
|
-
rulesetCycleNumber: 1,
|
|
1011
|
-
weight: weight,
|
|
1012
|
-
basedOnId: baseRuleset.id,
|
|
1013
|
-
start: mustStartAtOrAfter
|
|
1014
|
-
});
|
|
1015
|
-
} else {
|
|
1016
|
-
// Derive the correct next start time from the base.
|
|
1017
|
-
uint256 start = deriveStartFrom({
|
|
1018
|
-
baseRulesetStart: baseRuleset.start,
|
|
1019
|
-
baseRulesetDuration: baseRuleset.duration,
|
|
1020
|
-
mustStartAtOrAfter: mustStartAtOrAfter
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
// A weight of 1 is a special case that represents inheriting the cut weight of the previous
|
|
1024
|
-
// ruleset.
|
|
1025
|
-
weight = weight == 1
|
|
1026
|
-
? deriveWeightFrom({
|
|
1027
|
-
projectId: projectId,
|
|
1028
|
-
baseRulesetStart: baseRuleset.start,
|
|
1029
|
-
baseRulesetDuration: baseRuleset.duration,
|
|
1030
|
-
baseRulesetWeight: baseRuleset.weight,
|
|
1031
|
-
baseRulesetWeightCutPercent: baseRuleset.weightCutPercent,
|
|
1032
|
-
baseRulesetCacheId: baseRuleset.id,
|
|
1033
|
-
start: start
|
|
1034
|
-
})
|
|
1035
|
-
: weight;
|
|
1036
|
-
|
|
1037
|
-
// Derive the correct ruleset cycle number.
|
|
1038
|
-
uint256 rulesetCycleNumber = deriveCycleNumberFrom({
|
|
1039
|
-
baseRulesetCycleNumber: baseRuleset.cycleNumber,
|
|
1040
|
-
baseRulesetStart: baseRuleset.start,
|
|
1041
|
-
baseRulesetDuration: baseRuleset.duration,
|
|
1042
|
-
start: start
|
|
1043
|
-
});
|
|
1044
|
-
|
|
1045
|
-
// Update the intrinsic properties.
|
|
1046
|
-
_packAndStoreIntrinsicPropertiesOf({
|
|
1047
|
-
rulesetId: rulesetId,
|
|
1048
|
-
projectId: projectId,
|
|
1049
|
-
rulesetCycleNumber: rulesetCycleNumber,
|
|
1050
|
-
weight: weight,
|
|
1051
|
-
basedOnId: baseRuleset.id,
|
|
1052
|
-
start: start
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// Set the project's latest ruleset configuration.
|
|
1057
|
-
latestRulesetIdOf[projectId] = rulesetId;
|
|
1058
|
-
|
|
1059
|
-
emit RulesetInitialized({
|
|
1060
|
-
rulesetId: rulesetId, projectId: projectId, basedOnId: baseRuleset.id, caller: msg.sender
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
/// @notice Efficiently stores the provided intrinsic properties of a ruleset.
|
|
1065
|
-
/// @param rulesetId The `rulesetId` of the ruleset to pack and store for.
|
|
1066
|
-
/// @param projectId The ID of the project the ruleset belongs to.
|
|
1067
|
-
/// @param rulesetCycleNumber The cycle number of the ruleset.
|
|
1068
|
-
/// @param weight The weight of the ruleset.
|
|
1069
|
-
/// @param basedOnId The `rulesetId` of the ruleset this ruleset was based on.
|
|
1070
|
-
/// @param start The start time of this ruleset.
|
|
1071
|
-
function _packAndStoreIntrinsicPropertiesOf(
|
|
1072
|
-
uint256 rulesetId,
|
|
1073
|
-
uint256 projectId,
|
|
1074
|
-
uint256 rulesetCycleNumber,
|
|
1075
|
-
uint256 weight,
|
|
1076
|
-
uint256 basedOnId,
|
|
1077
|
-
uint256 start
|
|
1078
|
-
)
|
|
1079
|
-
internal
|
|
1080
|
-
{
|
|
1081
|
-
// `weight` in bits 0-111.
|
|
1082
|
-
uint256 packed = weight;
|
|
1083
|
-
|
|
1084
|
-
// `basedOnId` in bits 112-159.
|
|
1085
|
-
packed |= basedOnId << 112;
|
|
1086
|
-
|
|
1087
|
-
// `start` in bits 160-207.
|
|
1088
|
-
packed |= start << 160;
|
|
1089
|
-
|
|
1090
|
-
// cycle number in bits 208-255.
|
|
1091
|
-
packed |= rulesetCycleNumber << 208;
|
|
1092
|
-
|
|
1093
|
-
// Store the packed value.
|
|
1094
|
-
_packedIntrinsicPropertiesOf[projectId][rulesetId] = packed;
|
|
1095
|
-
}
|
|
1096
1096
|
}
|