@bananapus/core-v6 0.0.18 → 0.0.20
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 +3 -0
- package/ARCHITECTURE.md +24 -0
- package/AUDIT_INSTRUCTIONS.md +4 -2
- package/CHANGE_LOG.md +29 -1
- package/README.md +12 -2
- package/RISKS.md +10 -2
- package/SKILLS.md +9 -0
- package/USER_JOURNEYS.md +6 -0
- package/foundry.toml +1 -0
- package/package.json +1 -1
- package/src/JBController.sol +52 -5
- package/src/JBMultiTerminal.sol +197 -179
- package/src/JBTerminalStore.sol +367 -171
- package/src/interfaces/IJBCashOutTerminal.sol +30 -0
- package/src/interfaces/IJBController.sol +15 -0
- package/src/interfaces/IJBTerminal.sol +28 -0
- package/src/interfaces/IJBTerminalStore.sol +66 -0
- package/src/libraries/JBPayoutSplitGroupLib.sol +157 -0
- package/src/structs/JBCashOutHookSpecification.sol +2 -0
- package/src/structs/JBPayHookSpecification.sol +2 -0
- package/test/CoreExploitTests.t.sol +21 -10
- package/test/TestCashOutHooks.sol +6 -4
- package/test/TestDataHookFuzzing.sol +6 -2
- package/test/TestPayHooks.sol +1 -1
- package/test/TestRulesetQueueing.sol +4 -5
- package/test/TestRulesetQueuingStress.sol +5 -3
- package/test/TestTerminalPreviewParity.sol +208 -0
- package/test/fork/TestSequencerPriceFeedFork.sol +168 -0
- package/test/fork/TestTerminalPreviewParityFork.sol +109 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +116 -0
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +144 -25
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +11 -1
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +15 -2
- package/test/units/static/JBMultiTerminal/TestPay.sol +64 -2
- package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +116 -0
- package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +98 -0
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +11 -2
- package/test/units/static/JBRulesets/TestCurrentOf.sol +8 -6
- package/test/units/static/JBRulesets/TestRulesets.sol +25 -24
- package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +4 -17
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +49 -2
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +215 -0
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +475 -0
- package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +464 -0
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +113 -2
- package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +227 -5
|
@@ -6,6 +6,8 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
|
6
6
|
import {IJBPayHook} from "./IJBPayHook.sol";
|
|
7
7
|
import {JBAccountingContext} from "../structs/JBAccountingContext.sol";
|
|
8
8
|
import {JBAfterPayRecordedContext} from "../structs/JBAfterPayRecordedContext.sol";
|
|
9
|
+
import {JBPayHookSpecification} from "../structs/JBPayHookSpecification.sol";
|
|
10
|
+
import {JBRuleset} from "../structs/JBRuleset.sol";
|
|
9
11
|
|
|
10
12
|
/// @notice A terminal that accepts payments and can be migrated.
|
|
11
13
|
interface IJBTerminal is IERC165 {
|
|
@@ -102,6 +104,32 @@ interface IJBTerminal is IERC165 {
|
|
|
102
104
|
view
|
|
103
105
|
returns (uint256);
|
|
104
106
|
|
|
107
|
+
/// @notice Simulates paying a project through this terminal without modifying state.
|
|
108
|
+
/// @param projectId The ID of the project being paid.
|
|
109
|
+
/// @param token The token being paid in.
|
|
110
|
+
/// @param amount The amount of tokens being paid.
|
|
111
|
+
/// @param beneficiary The address to mint project tokens to.
|
|
112
|
+
/// @param metadata Extra data to pass along to the data hook and pay hooks.
|
|
113
|
+
/// @return ruleset The project's current ruleset.
|
|
114
|
+
/// @return beneficiaryTokenCount The number of project tokens that would be minted for the beneficiary.
|
|
115
|
+
/// @return reservedTokenCount The number of project tokens that would be reserved.
|
|
116
|
+
/// @return hookSpecifications Any pay hook specifications from the data hook.
|
|
117
|
+
function previewPayFor(
|
|
118
|
+
uint256 projectId,
|
|
119
|
+
address token,
|
|
120
|
+
uint256 amount,
|
|
121
|
+
address beneficiary,
|
|
122
|
+
bytes calldata metadata
|
|
123
|
+
)
|
|
124
|
+
external
|
|
125
|
+
view
|
|
126
|
+
returns (
|
|
127
|
+
JBRuleset memory ruleset,
|
|
128
|
+
uint256 beneficiaryTokenCount,
|
|
129
|
+
uint256 reservedTokenCount,
|
|
130
|
+
JBPayHookSpecification[] memory hookSpecifications
|
|
131
|
+
);
|
|
132
|
+
|
|
105
133
|
/// @notice Adds accounting contexts for a project's tokens.
|
|
106
134
|
/// @param projectId The ID of the project to add accounting contexts for.
|
|
107
135
|
/// @param accountingContexts The accounting contexts to add.
|
|
@@ -65,6 +65,22 @@ interface IJBTerminalStore {
|
|
|
65
65
|
view
|
|
66
66
|
returns (uint256);
|
|
67
67
|
|
|
68
|
+
/// @notice Returns the reclaimable surplus for a project across all terminals using all accounting contexts.
|
|
69
|
+
/// @param projectId The ID of the project.
|
|
70
|
+
/// @param cashOutCount The number of tokens being cashed out.
|
|
71
|
+
/// @param decimals The number of decimals to express the result with.
|
|
72
|
+
/// @param currency The currency to express the result in.
|
|
73
|
+
/// @return The reclaimable surplus amount.
|
|
74
|
+
function currentTotalReclaimableSurplusOf(
|
|
75
|
+
uint256 projectId,
|
|
76
|
+
uint256 cashOutCount,
|
|
77
|
+
uint256 decimals,
|
|
78
|
+
uint256 currency
|
|
79
|
+
)
|
|
80
|
+
external
|
|
81
|
+
view
|
|
82
|
+
returns (uint256);
|
|
83
|
+
|
|
68
84
|
/// @notice Returns the current surplus for a terminal and project.
|
|
69
85
|
/// @param terminal The terminal to get the surplus of.
|
|
70
86
|
/// @param projectId The ID of the project.
|
|
@@ -97,6 +113,56 @@ interface IJBTerminalStore {
|
|
|
97
113
|
view
|
|
98
114
|
returns (uint256);
|
|
99
115
|
|
|
116
|
+
/// @notice Simulates a cash out without modifying state.
|
|
117
|
+
/// @param holder The address cashing out.
|
|
118
|
+
/// @param projectId The ID of the project being cashed out from.
|
|
119
|
+
/// @param cashOutCount The number of project tokens being cashed out.
|
|
120
|
+
/// @param accountingContext The accounting context of the token being reclaimed.
|
|
121
|
+
/// @param balanceAccountingContexts The accounting contexts to include in the balance calculation.
|
|
122
|
+
/// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address.
|
|
123
|
+
/// @param metadata Extra data to pass along to the data hook.
|
|
124
|
+
/// @return ruleset The project's current ruleset.
|
|
125
|
+
/// @return reclaimAmount The amount that would be reclaimed.
|
|
126
|
+
/// @return cashOutTaxRate The cash out tax rate that would be applied.
|
|
127
|
+
/// @return hookSpecifications Any cash out hook specifications from the data hook.
|
|
128
|
+
function previewCashOutFrom(
|
|
129
|
+
address holder,
|
|
130
|
+
uint256 projectId,
|
|
131
|
+
uint256 cashOutCount,
|
|
132
|
+
JBAccountingContext calldata accountingContext,
|
|
133
|
+
JBAccountingContext[] calldata balanceAccountingContexts,
|
|
134
|
+
bool beneficiaryIsFeeless,
|
|
135
|
+
bytes calldata metadata
|
|
136
|
+
)
|
|
137
|
+
external
|
|
138
|
+
view
|
|
139
|
+
returns (
|
|
140
|
+
JBRuleset memory ruleset,
|
|
141
|
+
uint256 reclaimAmount,
|
|
142
|
+
uint256 cashOutTaxRate,
|
|
143
|
+
JBCashOutHookSpecification[] memory hookSpecifications
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
/// @notice Simulates a payment without modifying state.
|
|
147
|
+
/// @param payer The address of the payer.
|
|
148
|
+
/// @param amount The amount being paid.
|
|
149
|
+
/// @param projectId The ID of the project being paid.
|
|
150
|
+
/// @param beneficiary The address to mint project tokens to.
|
|
151
|
+
/// @param metadata Extra data to pass along to the data hook.
|
|
152
|
+
/// @return ruleset The project's current ruleset.
|
|
153
|
+
/// @return tokenCount The number of project tokens that would be minted, including reserved tokens.
|
|
154
|
+
/// @return hookSpecifications Any pay hook specifications from the data hook.
|
|
155
|
+
function previewPayFrom(
|
|
156
|
+
address payer,
|
|
157
|
+
JBTokenAmount memory amount,
|
|
158
|
+
uint256 projectId,
|
|
159
|
+
address beneficiary,
|
|
160
|
+
bytes calldata metadata
|
|
161
|
+
)
|
|
162
|
+
external
|
|
163
|
+
view
|
|
164
|
+
returns (JBRuleset memory ruleset, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications);
|
|
165
|
+
|
|
100
166
|
/// @notice Returns the amount of payout limit used by a terminal for a project in a given cycle.
|
|
101
167
|
/// @param terminal The terminal to get the used payout limit of.
|
|
102
168
|
/// @param projectId The ID of the project.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
5
|
+
|
|
6
|
+
import {IJBPayoutTerminal} from "../interfaces/IJBPayoutTerminal.sol";
|
|
7
|
+
import {IJBSplits} from "../interfaces/IJBSplits.sol";
|
|
8
|
+
import {IJBTerminalStore} from "../interfaces/IJBTerminalStore.sol";
|
|
9
|
+
import {JBSplit} from "../structs/JBSplit.sol";
|
|
10
|
+
import {JBConstants} from "./JBConstants.sol";
|
|
11
|
+
|
|
12
|
+
/// @notice Minimal callback surface used only by this library to call back into the terminal's `executePayout(...)`.
|
|
13
|
+
/// @dev Kept local to this file because `executePayout(...)` is an implementation detail, not a shared public
|
|
14
|
+
/// interface.
|
|
15
|
+
interface IJBPayoutSplitGroupExecutor {
|
|
16
|
+
function executePayout(
|
|
17
|
+
JBSplit calldata split,
|
|
18
|
+
uint256 projectId,
|
|
19
|
+
address token,
|
|
20
|
+
uint256 amount,
|
|
21
|
+
address originalMessageSender
|
|
22
|
+
)
|
|
23
|
+
external
|
|
24
|
+
returns (uint256 netPayoutAmount);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// @notice External library for payout split-group distribution extracted to reduce terminal bytecode.
|
|
28
|
+
/// @dev Called via DELEGATECALL from the terminal, so events are emitted from the terminal's address.
|
|
29
|
+
library JBPayoutSplitGroupLib {
|
|
30
|
+
event PayoutReverted(uint256 indexed projectId, JBSplit split, uint256 amount, bytes reason, address caller);
|
|
31
|
+
event SendPayoutToSplit(
|
|
32
|
+
uint256 indexed projectId,
|
|
33
|
+
uint256 indexed rulesetId,
|
|
34
|
+
uint256 indexed group,
|
|
35
|
+
JBSplit split,
|
|
36
|
+
uint256 amount,
|
|
37
|
+
uint256 netAmount,
|
|
38
|
+
address caller
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/// @notice Sends payouts to the payout splits group specified in a project's ruleset.
|
|
42
|
+
/// @param splits The splits contract to read splits from.
|
|
43
|
+
/// @param store The terminal store used to restore balance when a payout fails.
|
|
44
|
+
/// @param projectId The ID of the project to send the payouts of.
|
|
45
|
+
/// @param token The address of the token being paid out.
|
|
46
|
+
/// @param rulesetId The ID of the ruleset of the split group being paid.
|
|
47
|
+
/// @param amount The total amount being paid out.
|
|
48
|
+
/// @param caller The original caller of the terminal payout flow.
|
|
49
|
+
/// @return leftoverAmount The leftover amount after split payouts.
|
|
50
|
+
/// @return amountEligibleForFees The amount of payouts that are eligible for fees.
|
|
51
|
+
function sendPayoutsToSplitGroupOf(
|
|
52
|
+
IJBSplits splits,
|
|
53
|
+
IJBTerminalStore store,
|
|
54
|
+
uint256 projectId,
|
|
55
|
+
address token,
|
|
56
|
+
uint256 rulesetId,
|
|
57
|
+
uint256 amount,
|
|
58
|
+
address caller
|
|
59
|
+
)
|
|
60
|
+
external
|
|
61
|
+
returns (uint256 leftoverAmount, uint256 amountEligibleForFees)
|
|
62
|
+
{
|
|
63
|
+
// The total percentage available to split.
|
|
64
|
+
uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;
|
|
65
|
+
uint256 group = uint256(uint160(token));
|
|
66
|
+
|
|
67
|
+
// Get a reference to the project's payout splits.
|
|
68
|
+
JBSplit[] memory payoutSplits = splits.splitsOf({projectId: projectId, rulesetId: rulesetId, groupId: group});
|
|
69
|
+
|
|
70
|
+
leftoverAmount = amount;
|
|
71
|
+
|
|
72
|
+
// Transfer between all splits.
|
|
73
|
+
for (uint256 i; i < payoutSplits.length; i++) {
|
|
74
|
+
// Get a reference to the split being iterated on.
|
|
75
|
+
JBSplit memory split = payoutSplits[i];
|
|
76
|
+
|
|
77
|
+
// The amount to send to the split.
|
|
78
|
+
uint256 payoutAmount = mulDiv(leftoverAmount, split.percent, leftoverPercentage);
|
|
79
|
+
|
|
80
|
+
// The final payout amount after taking out any fees.
|
|
81
|
+
uint256 netPayoutAmount = _sendPayoutToSplit({
|
|
82
|
+
store: store, split: split, projectId: projectId, token: token, amount: payoutAmount, caller: caller
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// If the split hook is a feeless address, this payout doesn't incur a fee.
|
|
86
|
+
if (netPayoutAmount != 0 && netPayoutAmount != payoutAmount) {
|
|
87
|
+
amountEligibleForFees += payoutAmount;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (payoutAmount != 0) {
|
|
91
|
+
// Subtract from the amount to be sent to the beneficiary.
|
|
92
|
+
unchecked {
|
|
93
|
+
leftoverAmount -= payoutAmount;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
unchecked {
|
|
98
|
+
// Decrement the leftover percentage.
|
|
99
|
+
leftoverPercentage -= split.percent;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
emit SendPayoutToSplit({
|
|
103
|
+
projectId: projectId,
|
|
104
|
+
rulesetId: rulesetId,
|
|
105
|
+
group: group,
|
|
106
|
+
split: split,
|
|
107
|
+
amount: payoutAmount,
|
|
108
|
+
netAmount: netPayoutAmount,
|
|
109
|
+
caller: caller
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// @notice Sends a payout to a split.
|
|
115
|
+
/// @param store The terminal store used to restore balance when a payout fails.
|
|
116
|
+
/// @param split The split to pay.
|
|
117
|
+
/// @param projectId The ID of the project the split was specified by.
|
|
118
|
+
/// @param token The address of the token being paid out.
|
|
119
|
+
/// @param amount The total amount that the split is being paid.
|
|
120
|
+
/// @param caller The original caller of the terminal payout flow.
|
|
121
|
+
/// @return netPayoutAmount The amount sent to the split after subtracting fees.
|
|
122
|
+
function _sendPayoutToSplit(
|
|
123
|
+
IJBTerminalStore store,
|
|
124
|
+
JBSplit memory split,
|
|
125
|
+
uint256 projectId,
|
|
126
|
+
address token,
|
|
127
|
+
uint256 amount,
|
|
128
|
+
address caller
|
|
129
|
+
)
|
|
130
|
+
private
|
|
131
|
+
returns (uint256 netPayoutAmount)
|
|
132
|
+
{
|
|
133
|
+
// Failed split payouts consume the payout limit by design. The try-catch prevents a single
|
|
134
|
+
// split from DoS-ing the entire payout. Failed splits' amounts are returned to the project balance via
|
|
135
|
+
// `recordAddedBalanceFor`. Payout limit consumption is correct because the project authorized the
|
|
136
|
+
// distribution.
|
|
137
|
+
// slither-disable-next-line reentrancy-events
|
|
138
|
+
try IJBPayoutSplitGroupExecutor(address(this))
|
|
139
|
+
.executePayout({
|
|
140
|
+
split: split, projectId: projectId, token: token, amount: amount, originalMessageSender: caller
|
|
141
|
+
}) returns (
|
|
142
|
+
uint256 payoutAmount
|
|
143
|
+
) {
|
|
144
|
+
return payoutAmount;
|
|
145
|
+
} catch (bytes memory failureReason) {
|
|
146
|
+
emit PayoutReverted({
|
|
147
|
+
projectId: projectId, split: split, amount: amount, reason: failureReason, caller: caller
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Add balance back to the project.
|
|
151
|
+
store.recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
|
|
152
|
+
|
|
153
|
+
// Since the payout failed the netPayoutAmount is zero.
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -6,11 +6,13 @@ import {IJBCashOutHook} from "../interfaces/IJBCashOutHook.sol";
|
|
|
6
6
|
/// @notice A cash out hook specification sent from the ruleset's data hook back to the terminal. This specification is
|
|
7
7
|
/// fulfilled by the terminal.
|
|
8
8
|
/// @custom:member hook The cash out hook to use when fulfilling this specification.
|
|
9
|
+
/// @custom:member noop A flag indicating if the hook callback should be skipped.
|
|
9
10
|
/// @custom:member amount The amount to send to the hook.
|
|
10
11
|
/// @custom:member metadata Metadata to pass to the hook.
|
|
11
12
|
// forge-lint: disable-next-line(pascal-case-struct)
|
|
12
13
|
struct JBCashOutHookSpecification {
|
|
13
14
|
IJBCashOutHook hook;
|
|
15
|
+
bool noop;
|
|
14
16
|
uint256 amount;
|
|
15
17
|
bytes metadata;
|
|
16
18
|
}
|
|
@@ -6,11 +6,13 @@ import {IJBPayHook} from "../interfaces/IJBPayHook.sol";
|
|
|
6
6
|
/// @notice A pay hook specification sent from the ruleset's data hook back to the terminal. This specification is
|
|
7
7
|
/// fulfilled by the terminal.
|
|
8
8
|
/// @custom:member hook The pay hook to use when fulfilling this specification.
|
|
9
|
+
/// @custom:member noop A flag indicating if the hook callback should be skipped.
|
|
9
10
|
/// @custom:member amount The amount to send to the hook.
|
|
10
11
|
/// @custom:member metadata Metadata to pass the hook.
|
|
11
12
|
// forge-lint: disable-next-line(pascal-case-struct)
|
|
12
13
|
struct JBPayHookSpecification {
|
|
13
14
|
IJBPayHook hook;
|
|
15
|
+
bool noop;
|
|
14
16
|
uint256 amount;
|
|
15
17
|
bytes metadata;
|
|
16
18
|
}
|
|
@@ -358,7 +358,7 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
358
358
|
/// requires a call boundary to catch the panic.
|
|
359
359
|
function externalWriteToIndex1(JBPayHookSpecification[] memory specs) external pure {
|
|
360
360
|
// This will revert with Panic(0x32) -- array index out of bounds.
|
|
361
|
-
specs[1] = JBPayHookSpecification({hook: IJBPayHook(address(0)), amount: 0, metadata: ""});
|
|
361
|
+
specs[1] = JBPayHookSpecification({hook: IJBPayHook(address(0)), noop: false, amount: 0, metadata: ""});
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
/// @notice Verify that when BOTH hooks are present, the array size is 2 and no OOB occurs.
|
|
@@ -372,8 +372,10 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
372
372
|
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](arraySize);
|
|
373
373
|
|
|
374
374
|
// Both writes are valid.
|
|
375
|
-
hookSpecifications[0] =
|
|
376
|
-
|
|
375
|
+
hookSpecifications[0] =
|
|
376
|
+
JBPayHookSpecification({hook: IJBPayHook(address(0)), noop: false, amount: 0, metadata: ""});
|
|
377
|
+
hookSpecifications[1] =
|
|
378
|
+
JBPayHookSpecification({hook: IJBPayHook(address(0)), noop: false, amount: 0, metadata: ""});
|
|
377
379
|
}
|
|
378
380
|
|
|
379
381
|
/// @notice Verify that when neither hook is present, no write occurs.
|
|
@@ -1171,7 +1173,9 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
1171
1173
|
// Replicate REVDeployer logic exactly:
|
|
1172
1174
|
// if (usesTiered721Hook) hookSpecifications[0] = ...
|
|
1173
1175
|
if (usesT) {
|
|
1174
|
-
specs[0] = JBPayHookSpecification({
|
|
1176
|
+
specs[0] = JBPayHookSpecification({
|
|
1177
|
+
hook: IJBPayHook(address(0xdead)), noop: false, amount: 1 ether, metadata: ""
|
|
1178
|
+
});
|
|
1175
1179
|
}
|
|
1176
1180
|
|
|
1177
1181
|
// if (usesBuybackHook) hookSpecifications[1] = ... // ALWAYS index 1!
|
|
@@ -1182,8 +1186,9 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
1182
1186
|
this.externalWriteToIndex1(specs);
|
|
1183
1187
|
} else {
|
|
1184
1188
|
// usesT=true, usesB=true → size=2, writing to [1] → OK
|
|
1185
|
-
specs[1] =
|
|
1186
|
-
|
|
1189
|
+
specs[1] = JBPayHookSpecification({
|
|
1190
|
+
hook: IJBPayHook(address(0xbeef)), noop: false, amount: 2 ether, metadata: ""
|
|
1191
|
+
});
|
|
1187
1192
|
}
|
|
1188
1193
|
}
|
|
1189
1194
|
}
|
|
@@ -1496,6 +1501,10 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
1496
1501
|
|
|
1497
1502
|
/// @notice Verify processHeldFeesOf correctly handles partial unlock (some locked, some unlocked).
|
|
1498
1503
|
function test_partialLockedFees() public {
|
|
1504
|
+
// Use an explicit warp to a realistic timestamp to avoid via_ir reordering of block.timestamp reads.
|
|
1505
|
+
vm.warp(1_000_000);
|
|
1506
|
+
uint256 _startTime = 1_000_000;
|
|
1507
|
+
|
|
1499
1508
|
// Launch fee project.
|
|
1500
1509
|
JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
|
|
1501
1510
|
JBAccountingContext[] memory tokensToAccept = new JBAccountingContext[](1);
|
|
@@ -1615,7 +1624,9 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
1615
1624
|
});
|
|
1616
1625
|
|
|
1617
1626
|
// Warp 14 days (halfway through holding period).
|
|
1618
|
-
|
|
1627
|
+
uint256 _fee1Time = _startTime;
|
|
1628
|
+
uint256 _midPoint = _fee1Time + 14 days;
|
|
1629
|
+
vm.warp(_midPoint);
|
|
1619
1630
|
|
|
1620
1631
|
// Create second held fee (this one will be locked when we process at day 28).
|
|
1621
1632
|
vm.prank(_projectOwner);
|
|
@@ -1635,7 +1646,7 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
1635
1646
|
assertEq(heldFees.length, 2, "should have 2 held fees");
|
|
1636
1647
|
|
|
1637
1648
|
// Warp to 28 days + 1 from the start (first fee unlocked, second still locked 14 more days).
|
|
1638
|
-
vm.warp(
|
|
1649
|
+
vm.warp(_midPoint + 14 days + 1);
|
|
1639
1650
|
|
|
1640
1651
|
// Process all — should only process the first (unlocked) fee and stop at the second (locked).
|
|
1641
1652
|
_terminal.processHeldFeesOf(projectId, JBConstants.NATIVE_TOKEN, 100);
|
|
@@ -1644,8 +1655,8 @@ contract EdgeCases_Local is TestBaseWorkflow {
|
|
|
1644
1655
|
JBFee[] memory remainingFees = _terminal.heldFeesOf(projectId, JBConstants.NATIVE_TOKEN, 100);
|
|
1645
1656
|
assertEq(remainingFees.length, 1, "one fee should still be locked");
|
|
1646
1657
|
|
|
1647
|
-
// Warp past second fee's unlock.
|
|
1648
|
-
vm.warp(
|
|
1658
|
+
// Warp past second fee's unlock (second fee was created at _midPoint, unlocks at _midPoint + 28 days).
|
|
1659
|
+
vm.warp(_midPoint + 28 days + 1);
|
|
1649
1660
|
|
|
1650
1661
|
// Process the remaining fee.
|
|
1651
1662
|
_terminal.processHeldFeesOf(projectId, JBConstants.NATIVE_TOKEN, 100);
|
|
@@ -156,8 +156,9 @@ contract TestCashOutHooks_Local is TestBaseWorkflow {
|
|
|
156
156
|
// Reference cash out hook specifications.
|
|
157
157
|
JBCashOutHookSpecification[] memory _specifications = new JBCashOutHookSpecification[](1);
|
|
158
158
|
|
|
159
|
-
_specifications[0] =
|
|
160
|
-
|
|
159
|
+
_specifications[0] = JBCashOutHookSpecification({
|
|
160
|
+
hook: IJBCashOutHook(_cashOutHook), noop: false, amount: _halfPaid, metadata: ""
|
|
161
|
+
});
|
|
161
162
|
|
|
162
163
|
vm.startPrank(multisig());
|
|
163
164
|
// Set the hook as feeless.
|
|
@@ -260,8 +261,9 @@ contract TestCashOutHooks_Local is TestBaseWorkflow {
|
|
|
260
261
|
// Reference cash out hook specifications.
|
|
261
262
|
JBCashOutHookSpecification[] memory _specifications = new JBCashOutHookSpecification[](1);
|
|
262
263
|
|
|
263
|
-
_specifications[0] =
|
|
264
|
-
|
|
264
|
+
_specifications[0] = JBCashOutHookSpecification({
|
|
265
|
+
hook: IJBCashOutHook(_cashOutHook), noop: false, amount: _halfPaid, metadata: ""
|
|
266
|
+
});
|
|
265
267
|
|
|
266
268
|
uint256 _customCashOutTaxRate = JBConstants.MAX_CASH_OUT_TAX_RATE / 2;
|
|
267
269
|
uint256 _customCashOutCount = 1 * 10 ** 18;
|
|
@@ -206,6 +206,7 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
|
|
|
206
206
|
JBPayHookSpecification[] memory _specs = new JBPayHookSpecification[](1);
|
|
207
207
|
_specs[0] = JBPayHookSpecification({
|
|
208
208
|
hook: IJBPayHook(_payHook),
|
|
209
|
+
noop: false,
|
|
209
210
|
amount: _payAmount + 1, // exceeds the paid amount
|
|
210
211
|
metadata: ""
|
|
211
212
|
});
|
|
@@ -327,7 +328,9 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
|
|
|
327
328
|
jbFeelessAddresses().setFeelessAddress(_cashOutHook, true);
|
|
328
329
|
|
|
329
330
|
JBCashOutHookSpecification[] memory _specs = new JBCashOutHookSpecification[](1);
|
|
330
|
-
_specs[0] = JBCashOutHookSpecification({
|
|
331
|
+
_specs[0] = JBCashOutHookSpecification({
|
|
332
|
+
hook: IJBCashOutHook(_cashOutHook), noop: false, amount: _hookAmount, metadata: ""
|
|
333
|
+
});
|
|
331
334
|
|
|
332
335
|
// Override: cashOutTaxRate=0, half the tokens cashed out, original totalSupply.
|
|
333
336
|
vm.mockCall(
|
|
@@ -491,7 +494,8 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
|
|
|
491
494
|
// Cash out all tokens. With cashOutTaxRate=0, reclaim = full surplus = _payAmount.
|
|
492
495
|
// Set hook spec amount to 1 wei -- total = _payAmount + 1 which exceeds balance.
|
|
493
496
|
JBCashOutHookSpecification[] memory _specs = new JBCashOutHookSpecification[](1);
|
|
494
|
-
_specs[0] =
|
|
497
|
+
_specs[0] =
|
|
498
|
+
JBCashOutHookSpecification({hook: IJBCashOutHook(makeAddr("hook")), noop: false, amount: 1, metadata: ""});
|
|
495
499
|
|
|
496
500
|
vm.mockCall(
|
|
497
501
|
_DATA_HOOK,
|
package/test/TestPayHooks.sol
CHANGED
|
@@ -136,7 +136,7 @@ contract TestPayHooks_Local is TestBaseWorkflow {
|
|
|
136
136
|
|
|
137
137
|
// Package up the specification struct.
|
|
138
138
|
_specifications[i] = JBPayHookSpecification({
|
|
139
|
-
hook: IJBPayHook(_hookAddress), amount: _payHookAmounts[i], metadata: _hookMetadata
|
|
139
|
+
hook: IJBPayHook(_hookAddress), noop: false, amount: _payHookAmounts[i], metadata: _hookMetadata
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
// Keep a reference to the data that'll be received by the hook.
|
|
@@ -39,7 +39,7 @@ contract TestRulesetQueuing_Local is TestBaseWorkflow {
|
|
|
39
39
|
super.setUp();
|
|
40
40
|
|
|
41
41
|
// Foundry defaults block.timestamp to 1, which causes underflow in tests using past timestamps.
|
|
42
|
-
vm.warp(
|
|
42
|
+
vm.warp(1_000_001);
|
|
43
43
|
|
|
44
44
|
_terminal = jbMultiTerminal();
|
|
45
45
|
_controller = jbController();
|
|
@@ -637,9 +637,6 @@ contract TestRulesetQueuing_Local is TestBaseWorkflow {
|
|
|
637
637
|
uint112 _weightFirstQueued = uint112(1234 * 10 ** 18);
|
|
638
638
|
uint112 _weightSecondQueued = uint112(6969 * 10 ** 18);
|
|
639
639
|
|
|
640
|
-
// Keep a reference to the expected ruleset IDs (timestamps).
|
|
641
|
-
uint256 _initialRulesetId = block.timestamp;
|
|
642
|
-
|
|
643
640
|
// Package up a config.
|
|
644
641
|
JBRulesetConfig[] memory _rulesetConfig = new JBRulesetConfig[](1);
|
|
645
642
|
_rulesetConfig[0].mustStartAtOrAfter = 0;
|
|
@@ -657,10 +654,12 @@ contract TestRulesetQueuing_Local is TestBaseWorkflow {
|
|
|
657
654
|
// Get the ruleset.
|
|
658
655
|
JBRuleset memory _ruleset = jbRulesets().currentOf(projectId);
|
|
659
656
|
|
|
657
|
+
// Keep a reference to the expected ruleset IDs (use returned id to avoid via_ir reordering of block.timestamp).
|
|
658
|
+
uint256 _initialRulesetId = _ruleset.id;
|
|
659
|
+
|
|
660
660
|
// Make sure the first ruleset has begun.
|
|
661
661
|
assertEq(_ruleset.cycleNumber, 1);
|
|
662
662
|
assertEq(_ruleset.weight, _weightInitial);
|
|
663
|
-
assertEq(_ruleset.id, block.timestamp);
|
|
664
663
|
|
|
665
664
|
// Package up a new config.
|
|
666
665
|
JBRulesetConfig[] memory _firstQueued = new JBRulesetConfig[](1);
|
|
@@ -236,10 +236,11 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
|
|
|
236
236
|
/// @notice Mid-cycle queuing: new ruleset starts at next duration boundary.
|
|
237
237
|
function test_midCycleQueuing_snapsToNextBoundary() external {
|
|
238
238
|
uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
|
|
239
|
-
|
|
239
|
+
// Capture originalStart from the actual ruleset (avoid via_ir reordering of block.timestamp).
|
|
240
|
+
uint256 originalStart = _rulesets.currentOf(pid).start;
|
|
240
241
|
|
|
241
242
|
// Warp to mid-cycle (day 3 of 7).
|
|
242
|
-
vm.warp(
|
|
243
|
+
vm.warp(originalStart + 3 days);
|
|
243
244
|
|
|
244
245
|
_queueRuleset(pid, 0, SEVEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(0)));
|
|
245
246
|
|
|
@@ -573,7 +574,8 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
|
|
|
573
574
|
/// @notice deriveStartFrom one second after boundary: snaps to next boundary.
|
|
574
575
|
function test_deriveStartFrom_oneSecondAfterBoundary_snapsToNext() external {
|
|
575
576
|
uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
|
|
576
|
-
|
|
577
|
+
// Capture originalStart from the actual ruleset (avoid via_ir reordering of block.timestamp).
|
|
578
|
+
uint256 originalStart = _rulesets.currentOf(pid).start;
|
|
577
579
|
|
|
578
580
|
// 1 second after first boundary -> should snap to second boundary.
|
|
579
581
|
_queueRuleset(
|