@bananapus/core-v6 0.0.21 → 0.0.23
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 +0 -1
- package/AUDIT_INSTRUCTIONS.md +1 -1
- package/CHANGE_LOG.md +3 -3
- package/RISKS.md +3 -3
- package/SKILLS.md +8 -8
- package/USER_JOURNEYS.md +1 -1
- package/foundry.toml +0 -1
- package/package.json +1 -1
- package/src/JBMultiTerminal.sol +92 -192
- package/src/JBTerminalStore.sol +414 -256
- package/src/interfaces/IJBMultiTerminal.sol +0 -4
- package/src/interfaces/IJBTerminal.sol +4 -4
- package/src/interfaces/IJBTerminalStore.sol +65 -33
- package/src/libraries/JBPayoutSplitGroupLib.sol +0 -1
- package/src/libraries/JBSurplus.sol +3 -4
- package/test/ComprehensiveInvariant.t.sol +5 -7
- package/test/CoreExploitTests.t.sol +18 -23
- package/test/TestCashOut.sol +6 -6
- package/test/TestMultiTerminalSurplus.sol +4 -4
- package/test/TestMultiTokenSurplus.sol +6 -23
- package/test/TestTerminalMigration.sol +2 -7
- package/test/fork/TestSequencerPriceFeedFork.sol +1 -1
- package/test/fork/TestTerminalPreviewParityFork.sol +0 -1
- package/test/invariants/TerminalStoreInvariant.t.sol +5 -7
- package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +1 -2
- package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +23 -24
- package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +79 -119
- package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +33 -26
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +32 -27
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +22 -4
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +8 -5
- package/test/units/static/JBMultiTerminal/TestPay.sol +41 -33
- package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +19 -18
- package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +38 -22
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +9 -6
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +4 -4
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +37 -32
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +5 -20
- package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +17 -0
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +120 -246
- package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +29 -7
- package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +88 -20
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +30 -29
- package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +46 -16
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +24 -53
- package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +24 -4
- package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +14 -4
- package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +21 -3
package/src/JBTerminalStore.sol
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
4
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
5
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
5
6
|
|
|
6
7
|
import {IJBController} from "./interfaces/IJBController.sol";
|
|
7
8
|
import {IJBDirectory} from "./interfaces/IJBDirectory.sol";
|
|
@@ -10,9 +11,9 @@ import {IJBRulesetDataHook} from "./interfaces/IJBRulesetDataHook.sol";
|
|
|
10
11
|
import {IJBRulesets} from "./interfaces/IJBRulesets.sol";
|
|
11
12
|
import {IJBTerminal} from "./interfaces/IJBTerminal.sol";
|
|
12
13
|
import {IJBTerminalStore} from "./interfaces/IJBTerminalStore.sol";
|
|
14
|
+
import {JBCashOuts} from "./libraries/JBCashOuts.sol";
|
|
13
15
|
import {JBConstants} from "./libraries/JBConstants.sol";
|
|
14
16
|
import {JBFixedPointNumber} from "./libraries/JBFixedPointNumber.sol";
|
|
15
|
-
import {JBCashOuts} from "./libraries/JBCashOuts.sol";
|
|
16
17
|
import {JBRulesetMetadataResolver} from "./libraries/JBRulesetMetadataResolver.sol";
|
|
17
18
|
import {JBSurplus} from "./libraries/JBSurplus.sol";
|
|
18
19
|
import {JBAccountingContext} from "./structs/JBAccountingContext.sol";
|
|
@@ -34,6 +35,9 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
34
35
|
// --------------------------- custom errors ------------------------- //
|
|
35
36
|
//*********************************************************************//
|
|
36
37
|
|
|
38
|
+
error JBTerminalStore_AccountingContextAlreadySet(address token);
|
|
39
|
+
error JBTerminalStore_AccountingContextDecimalsMismatch();
|
|
40
|
+
error JBTerminalStore_AddingAccountingContextNotAllowed();
|
|
37
41
|
error JBTerminalStore_InadequateControllerAllowance(uint256 amount, uint256 allowance);
|
|
38
42
|
error JBTerminalStore_InadequateControllerPayoutLimit(uint256 amount, uint256 limit);
|
|
39
43
|
error JBTerminalStore_InadequateTerminalStoreBalance(uint256 amount, uint256 balance);
|
|
@@ -44,6 +48,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
44
48
|
error JBTerminalStore_RulesetPaymentPaused();
|
|
45
49
|
error JBTerminalStore_TerminalMigrationNotAllowed();
|
|
46
50
|
error JBTerminalStore_Uint224Overflow(uint256 value);
|
|
51
|
+
error JBTerminalStore_ZeroAccountingContextCurrency();
|
|
47
52
|
|
|
48
53
|
//*********************************************************************//
|
|
49
54
|
// -------------------------- internal constants --------------------- //
|
|
@@ -120,6 +125,22 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
120
125
|
public
|
|
121
126
|
override usedSurplusAllowanceOf;
|
|
122
127
|
|
|
128
|
+
//*********************************************************************//
|
|
129
|
+
// --------------------- internal stored properties ------------------ //
|
|
130
|
+
//*********************************************************************//
|
|
131
|
+
|
|
132
|
+
/// @notice The accounting context for a terminal's project token.
|
|
133
|
+
/// @custom:param terminal The terminal the accounting context applies to.
|
|
134
|
+
/// @custom:param projectId The ID of the project.
|
|
135
|
+
/// @custom:param token The token to get the accounting context for.
|
|
136
|
+
mapping(address terminal => mapping(uint256 projectId => mapping(address token => JBAccountingContext))) internal
|
|
137
|
+
_accountingContextForTokenOf;
|
|
138
|
+
|
|
139
|
+
/// @notice A list of accounting contexts for each terminal's project.
|
|
140
|
+
/// @custom:param terminal The terminal the accounting contexts apply to.
|
|
141
|
+
/// @custom:param projectId The ID of the project.
|
|
142
|
+
mapping(address terminal => mapping(uint256 projectId => JBAccountingContext[])) internal _accountingContextsOf;
|
|
143
|
+
|
|
123
144
|
//*********************************************************************//
|
|
124
145
|
// -------------------------- constructor ---------------------------- //
|
|
125
146
|
//*********************************************************************//
|
|
@@ -137,6 +158,64 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
137
158
|
// ---------------------- external transactions ---------------------- //
|
|
138
159
|
//*********************************************************************//
|
|
139
160
|
|
|
161
|
+
/// @notice Records accounting contexts for a terminal's project tokens.
|
|
162
|
+
/// @dev Uses msg.sender as the terminal.
|
|
163
|
+
/// @param projectId The ID of the project.
|
|
164
|
+
/// @param contexts The accounting contexts to record.
|
|
165
|
+
function recordAccountingContextOf(uint256 projectId, JBAccountingContext[] calldata contexts) external override {
|
|
166
|
+
// Get a reference to the project's current ruleset.
|
|
167
|
+
JBRuleset memory ruleset = RULESETS.currentOf(projectId);
|
|
168
|
+
|
|
169
|
+
// Make sure that if there's a ruleset, it allows adding accounting contexts.
|
|
170
|
+
if (ruleset.id != 0 && !ruleset.allowAddAccountingContext()) {
|
|
171
|
+
revert JBTerminalStore_AddingAccountingContextNotAllowed();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Record each accounting context.
|
|
175
|
+
for (uint256 i; i < contexts.length; i++) {
|
|
176
|
+
JBAccountingContext calldata context = contexts[i];
|
|
177
|
+
|
|
178
|
+
// Make sure the token accounting context isn't already set.
|
|
179
|
+
if (_accountingContextForTokenOf[msg.sender][projectId][context.token].token != address(0)) {
|
|
180
|
+
revert JBTerminalStore_AccountingContextAlreadySet(context.token);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Keep track of a flag indicating if we know the provided decimals are incorrect.
|
|
184
|
+
bool knownInvalidDecimals;
|
|
185
|
+
|
|
186
|
+
// Check if the token is the native token and has the correct decimals.
|
|
187
|
+
if (context.token == JBConstants.NATIVE_TOKEN && context.decimals != 18) {
|
|
188
|
+
knownInvalidDecimals = true;
|
|
189
|
+
} else if (context.token != JBConstants.NATIVE_TOKEN && context.token.code.length > 0) {
|
|
190
|
+
// slither-disable-next-line calls-loop
|
|
191
|
+
try IERC20Metadata(context.token).decimals() returns (uint8 decimals) {
|
|
192
|
+
if (context.decimals != decimals) {
|
|
193
|
+
knownInvalidDecimals = true;
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
// The token didn't support `decimals`.
|
|
197
|
+
// @dev Non-standard ERC20s that revert on `decimals()` will bypass decimal validation.
|
|
198
|
+
// The caller is responsible for providing the correct decimals for such tokens.
|
|
199
|
+
knownInvalidDecimals = false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Make sure the decimals are correct.
|
|
204
|
+
if (knownInvalidDecimals) {
|
|
205
|
+
revert JBTerminalStore_AccountingContextDecimalsMismatch();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Make sure the currency is non-zero.
|
|
209
|
+
if (context.currency == 0) revert JBTerminalStore_ZeroAccountingContextCurrency();
|
|
210
|
+
|
|
211
|
+
// Store the accounting context.
|
|
212
|
+
_accountingContextForTokenOf[msg.sender][projectId][context.token] = context;
|
|
213
|
+
|
|
214
|
+
// Add the context to the list.
|
|
215
|
+
_accountingContextsOf[msg.sender][projectId].push(context);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
140
219
|
/// @notice Records funds being added to a project's balance.
|
|
141
220
|
/// @param projectId The ID of the project which funds are being added to the balance of.
|
|
142
221
|
/// @param token The token being added to the balance.
|
|
@@ -155,9 +234,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
155
234
|
/// @param holder The account that is cashing out tokens.
|
|
156
235
|
/// @param projectId The ID of the project being cashing out from.
|
|
157
236
|
/// @param cashOutCount The number of project tokens to cash out, as a fixed point number with 18 decimals.
|
|
158
|
-
/// @param
|
|
159
|
-
/// @param balanceAccountingContexts The accounting contexts of the tokens whose balances should contribute to the
|
|
160
|
-
/// surplus being reclaimed from.
|
|
237
|
+
/// @param tokenToReclaim The token being reclaimed by the cash out.
|
|
161
238
|
/// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address. Passed through to data
|
|
162
239
|
/// hooks so they can skip their own fees when value stays in the protocol (e.g. project-to-project routing).
|
|
163
240
|
/// @param metadata Bytes to send to the data hook, if the project's current ruleset specifies one.
|
|
@@ -172,8 +249,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
172
249
|
address holder,
|
|
173
250
|
uint256 projectId,
|
|
174
251
|
uint256 cashOutCount,
|
|
175
|
-
|
|
176
|
-
JBAccountingContext[] calldata balanceAccountingContexts,
|
|
252
|
+
address tokenToReclaim,
|
|
177
253
|
bool beneficiaryIsFeeless,
|
|
178
254
|
bytes memory metadata
|
|
179
255
|
)
|
|
@@ -191,8 +267,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
191
267
|
holder: holder,
|
|
192
268
|
projectId: projectId,
|
|
193
269
|
cashOutCount: cashOutCount,
|
|
194
|
-
|
|
195
|
-
balanceAccountingContexts: balanceAccountingContexts,
|
|
270
|
+
tokenToReclaim: tokenToReclaim,
|
|
196
271
|
beneficiaryIsFeeless: beneficiaryIsFeeless,
|
|
197
272
|
metadata: metadata
|
|
198
273
|
});
|
|
@@ -211,17 +286,17 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
211
286
|
}
|
|
212
287
|
|
|
213
288
|
// The amount being reclaimed must be within the project's balance.
|
|
214
|
-
if (balanceDiff > balanceOf[msg.sender][projectId][
|
|
289
|
+
if (balanceDiff > balanceOf[msg.sender][projectId][tokenToReclaim]) {
|
|
215
290
|
revert JBTerminalStore_InadequateTerminalStoreBalance(
|
|
216
|
-
balanceDiff, balanceOf[msg.sender][projectId][
|
|
291
|
+
balanceDiff, balanceOf[msg.sender][projectId][tokenToReclaim]
|
|
217
292
|
);
|
|
218
293
|
}
|
|
219
294
|
|
|
220
295
|
// Remove the reclaimed funds from the project's balance.
|
|
221
296
|
if (balanceDiff != 0) {
|
|
222
297
|
unchecked {
|
|
223
|
-
balanceOf[msg.sender][projectId][
|
|
224
|
-
balanceOf[msg.sender][projectId][
|
|
298
|
+
balanceOf[msg.sender][projectId][tokenToReclaim] =
|
|
299
|
+
balanceOf[msg.sender][projectId][tokenToReclaim] - balanceDiff;
|
|
225
300
|
}
|
|
226
301
|
}
|
|
227
302
|
}
|
|
@@ -273,7 +348,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
273
348
|
/// This is safe because the entire transaction reverts atomically if the validation fails, but callers should
|
|
274
349
|
/// be aware of this ordering.
|
|
275
350
|
/// @param projectId The ID of the project that is paying out funds.
|
|
276
|
-
/// @param
|
|
351
|
+
/// @param token The token being paid out.
|
|
277
352
|
/// @param amount The amount to pay out (use from the payout limit), as a fixed point number.
|
|
278
353
|
/// @param currency The currency of the `amount`. This must match the project's current ruleset's currency.
|
|
279
354
|
/// @return ruleset The ruleset the payout was made during, as a `JBRuleset` struct.
|
|
@@ -281,7 +356,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
281
356
|
/// decimals as its relative terminal.
|
|
282
357
|
function recordPayoutFor(
|
|
283
358
|
uint256 projectId,
|
|
284
|
-
|
|
359
|
+
address token,
|
|
285
360
|
uint256 amount,
|
|
286
361
|
uint256 currency
|
|
287
362
|
)
|
|
@@ -289,53 +364,50 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
289
364
|
override
|
|
290
365
|
returns (JBRuleset memory ruleset, uint256 amountPaidOut)
|
|
291
366
|
{
|
|
367
|
+
// Look up the accounting context from storage.
|
|
368
|
+
JBAccountingContext memory accountingContext = _accountingContextForTokenOf[msg.sender][projectId][token];
|
|
369
|
+
|
|
292
370
|
// Get a reference to the project's current ruleset.
|
|
293
371
|
ruleset = RULESETS.currentOf(projectId);
|
|
294
372
|
|
|
295
373
|
// Convert the amount to the balance's currency.
|
|
296
374
|
amountPaidOut = (currency == accountingContext.currency)
|
|
297
375
|
? amount
|
|
298
|
-
: mulDiv(
|
|
299
|
-
amount,
|
|
300
|
-
10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
301
|
-
// fidelity as possible when converting.
|
|
302
|
-
PRICES.pricePerUnitOf({
|
|
376
|
+
: mulDiv({
|
|
377
|
+
x: amount,
|
|
378
|
+
y: 10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
379
|
+
// `_amount`'s fidelity as possible when converting.
|
|
380
|
+
denominator: PRICES.pricePerUnitOf({
|
|
303
381
|
projectId: projectId,
|
|
304
382
|
pricingCurrency: currency,
|
|
305
383
|
unitCurrency: accountingContext.currency,
|
|
306
384
|
decimals: _MAX_FIXED_POINT_FIDELITY
|
|
307
385
|
})
|
|
308
|
-
);
|
|
386
|
+
});
|
|
309
387
|
|
|
310
388
|
// The amount being paid out must be available.
|
|
311
|
-
if (amountPaidOut > balanceOf[msg.sender][projectId][
|
|
389
|
+
if (amountPaidOut > balanceOf[msg.sender][projectId][token]) {
|
|
312
390
|
revert JBTerminalStore_InadequateTerminalStoreBalance(
|
|
313
|
-
amountPaidOut, balanceOf[msg.sender][projectId][
|
|
391
|
+
amountPaidOut, balanceOf[msg.sender][projectId][token]
|
|
314
392
|
);
|
|
315
393
|
}
|
|
316
394
|
|
|
317
395
|
// Removed the paid out funds from the project's token balance.
|
|
318
396
|
unchecked {
|
|
319
|
-
balanceOf[msg.sender][projectId][
|
|
320
|
-
balanceOf[msg.sender][projectId][accountingContext.token] - amountPaidOut;
|
|
397
|
+
balanceOf[msg.sender][projectId][token] = balanceOf[msg.sender][projectId][token] - amountPaidOut;
|
|
321
398
|
}
|
|
322
399
|
|
|
323
400
|
// The new total amount which has been paid out during this ruleset.
|
|
324
401
|
uint256 newUsedPayoutLimitOf =
|
|
325
|
-
usedPayoutLimitOf[msg.sender][projectId][
|
|
402
|
+
usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] + amount;
|
|
326
403
|
|
|
327
404
|
// Store the new amount.
|
|
328
|
-
usedPayoutLimitOf[msg.sender][projectId][
|
|
329
|
-
newUsedPayoutLimitOf;
|
|
405
|
+
usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] = newUsedPayoutLimitOf;
|
|
330
406
|
|
|
331
407
|
// Amount must be within what is still available to pay out.
|
|
332
408
|
uint256 payoutLimit = IJBController(address(DIRECTORY.controllerOf(projectId))).FUND_ACCESS_LIMITS()
|
|
333
409
|
.payoutLimitOf({
|
|
334
|
-
projectId: projectId,
|
|
335
|
-
rulesetId: ruleset.id,
|
|
336
|
-
terminal: msg.sender,
|
|
337
|
-
token: accountingContext.token,
|
|
338
|
-
currency: currency
|
|
410
|
+
projectId: projectId, rulesetId: ruleset.id, terminal: msg.sender, token: token, currency: currency
|
|
339
411
|
});
|
|
340
412
|
|
|
341
413
|
// Make sure the new used amount is within the payout limit.
|
|
@@ -368,8 +440,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
368
440
|
/// @notice Records a use of a project's surplus allowance.
|
|
369
441
|
/// @dev When surplus allowance is "used", it is taken out of the project's surplus within a terminal.
|
|
370
442
|
/// @param projectId The ID of the project to use the surplus allowance of.
|
|
371
|
-
/// @param
|
|
372
|
-
/// allowance being reclaimed from.
|
|
443
|
+
/// @param token The token whose balances should contribute to the surplus allowance being reclaimed from.
|
|
373
444
|
/// @param amount The amount to use from the surplus allowance, as a fixed point number.
|
|
374
445
|
/// @param currency The currency of the `amount`. Must match the currency of the surplus allowance.
|
|
375
446
|
/// @return ruleset The ruleset during the surplus allowance is being used during, as a `JBRuleset` struct.
|
|
@@ -377,7 +448,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
377
448
|
/// as its relative terminal.
|
|
378
449
|
function recordUsedAllowanceOf(
|
|
379
450
|
uint256 projectId,
|
|
380
|
-
|
|
451
|
+
address token,
|
|
381
452
|
uint256 amount,
|
|
382
453
|
uint256 currency
|
|
383
454
|
)
|
|
@@ -385,23 +456,26 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
385
456
|
override
|
|
386
457
|
returns (JBRuleset memory ruleset, uint256 usedAmount)
|
|
387
458
|
{
|
|
459
|
+
// Look up the accounting context from storage.
|
|
460
|
+
JBAccountingContext memory accountingContext = _accountingContextForTokenOf[msg.sender][projectId][token];
|
|
461
|
+
|
|
388
462
|
// Get a reference to the project's current ruleset.
|
|
389
463
|
ruleset = RULESETS.currentOf(projectId);
|
|
390
464
|
|
|
391
465
|
// Convert the amount to this store's terminal's token.
|
|
392
466
|
usedAmount = currency == accountingContext.currency
|
|
393
467
|
? amount
|
|
394
|
-
: mulDiv(
|
|
395
|
-
amount,
|
|
396
|
-
10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
397
|
-
// fidelity as possible when converting.
|
|
398
|
-
PRICES.pricePerUnitOf({
|
|
468
|
+
: mulDiv({
|
|
469
|
+
x: amount,
|
|
470
|
+
y: 10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
471
|
+
// `amount`'s fidelity as possible when converting.
|
|
472
|
+
denominator: PRICES.pricePerUnitOf({
|
|
399
473
|
projectId: projectId,
|
|
400
474
|
pricingCurrency: currency,
|
|
401
475
|
unitCurrency: accountingContext.currency,
|
|
402
476
|
decimals: _MAX_FIXED_POINT_FIDELITY
|
|
403
477
|
})
|
|
404
|
-
);
|
|
478
|
+
});
|
|
405
479
|
|
|
406
480
|
// Set the token being used as the only one to look for surplus within.
|
|
407
481
|
JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
|
|
@@ -420,25 +494,19 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
420
494
|
if (usedAmount > surplus) revert JBTerminalStore_InadequateTerminalStoreBalance(usedAmount, surplus);
|
|
421
495
|
|
|
422
496
|
// Update the project's balance.
|
|
423
|
-
balanceOf[msg.sender][projectId][
|
|
424
|
-
balanceOf[msg.sender][projectId][accountingContext.token] - usedAmount;
|
|
497
|
+
balanceOf[msg.sender][projectId][token] = balanceOf[msg.sender][projectId][token] - usedAmount;
|
|
425
498
|
|
|
426
499
|
// Get a reference to the new used surplus allowance for this ruleset ID.
|
|
427
500
|
uint256 newUsedSurplusAllowanceOf =
|
|
428
|
-
usedSurplusAllowanceOf[msg.sender][projectId][
|
|
501
|
+
usedSurplusAllowanceOf[msg.sender][projectId][token][ruleset.id][currency] + amount;
|
|
429
502
|
|
|
430
503
|
// Store the incremented value.
|
|
431
|
-
usedSurplusAllowanceOf[msg.sender][projectId][
|
|
432
|
-
newUsedSurplusAllowanceOf;
|
|
504
|
+
usedSurplusAllowanceOf[msg.sender][projectId][token][ruleset.id][currency] = newUsedSurplusAllowanceOf;
|
|
433
505
|
|
|
434
506
|
// There must be sufficient surplus allowance available.
|
|
435
507
|
uint256 surplusAllowance = IJBController(address(DIRECTORY.controllerOf(projectId))).FUND_ACCESS_LIMITS()
|
|
436
508
|
.surplusAllowanceOf({
|
|
437
|
-
projectId: projectId,
|
|
438
|
-
rulesetId: ruleset.id,
|
|
439
|
-
terminal: msg.sender,
|
|
440
|
-
token: accountingContext.token,
|
|
441
|
-
currency: currency
|
|
509
|
+
projectId: projectId, rulesetId: ruleset.id, terminal: msg.sender, token: token, currency: currency
|
|
442
510
|
});
|
|
443
511
|
|
|
444
512
|
// Make sure the new used amount is within the allowance.
|
|
@@ -451,6 +519,40 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
451
519
|
// ------------------------- external views -------------------------- //
|
|
452
520
|
//*********************************************************************//
|
|
453
521
|
|
|
522
|
+
/// @notice Returns the accounting context for a terminal's project token.
|
|
523
|
+
/// @param terminal The terminal the accounting context applies to.
|
|
524
|
+
/// @param projectId The ID of the project.
|
|
525
|
+
/// @param token The token to get the accounting context for.
|
|
526
|
+
/// @return The accounting context.
|
|
527
|
+
function accountingContextOf(
|
|
528
|
+
address terminal,
|
|
529
|
+
uint256 projectId,
|
|
530
|
+
address token
|
|
531
|
+
)
|
|
532
|
+
external
|
|
533
|
+
view
|
|
534
|
+
override
|
|
535
|
+
returns (JBAccountingContext memory)
|
|
536
|
+
{
|
|
537
|
+
return _accountingContextForTokenOf[terminal][projectId][token];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/// @notice Returns all accounting contexts for a terminal's project.
|
|
541
|
+
/// @param terminal The terminal the accounting contexts apply to.
|
|
542
|
+
/// @param projectId The ID of the project.
|
|
543
|
+
/// @return The accounting contexts.
|
|
544
|
+
function accountingContextsOf(
|
|
545
|
+
address terminal,
|
|
546
|
+
uint256 projectId
|
|
547
|
+
)
|
|
548
|
+
external
|
|
549
|
+
view
|
|
550
|
+
override
|
|
551
|
+
returns (JBAccountingContext[] memory)
|
|
552
|
+
{
|
|
553
|
+
return _accountingContextsOf[terminal][projectId];
|
|
554
|
+
}
|
|
555
|
+
|
|
454
556
|
/// @notice Returns the number of surplus terminal tokens that would be reclaimed by cashing out a given project's
|
|
455
557
|
/// tokens based on its current ruleset and the given total project token supply and total terminal token surplus.
|
|
456
558
|
/// @param projectId The ID of the project whose project tokens would be cashed out.
|
|
@@ -489,17 +591,13 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
489
591
|
});
|
|
490
592
|
}
|
|
491
593
|
|
|
492
|
-
/// @notice Returns the number of surplus terminal tokens that would be reclaimed from
|
|
493
|
-
/// given number of tokens,
|
|
494
|
-
/// @dev The returned amount in terms of the specified `terminal`'s base currency.
|
|
495
|
-
/// @dev The returned amount is represented as a fixed point number with the same amount of decimals as the
|
|
496
|
-
/// specified terminal.
|
|
594
|
+
/// @notice Returns the number of surplus terminal tokens that would be reclaimed from terminals by cashing out a
|
|
595
|
+
/// given number of tokens, considering only specific tokens.
|
|
497
596
|
/// @param projectId The ID of the project whose tokens would be cashed out.
|
|
498
597
|
/// @param cashOutCount The number of tokens that would be cashed out, as a fixed point number with 18 decimals.
|
|
499
598
|
/// @param terminals The terminals that would be cashed out from. If this is an empty array, surplus within all
|
|
500
599
|
/// the project's terminals are considered.
|
|
501
|
-
/// @param
|
|
502
|
-
/// an empty array to use all of the project's accounting contexts.
|
|
600
|
+
/// @param tokens The tokens to include in the surplus calculation.
|
|
503
601
|
/// @param decimals The number of decimals to include in the resulting fixed point number.
|
|
504
602
|
/// @param currency The currency that the resulting number will be in terms of.
|
|
505
603
|
/// @return The amount of surplus terminal tokens that would be reclaimed by cashing out `cashOutCount`
|
|
@@ -508,7 +606,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
508
606
|
uint256 projectId,
|
|
509
607
|
uint256 cashOutCount,
|
|
510
608
|
IJBTerminal[] calldata terminals,
|
|
511
|
-
|
|
609
|
+
address[] calldata tokens,
|
|
512
610
|
uint256 decimals,
|
|
513
611
|
uint256 currency
|
|
514
612
|
)
|
|
@@ -517,18 +615,9 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
517
615
|
override
|
|
518
616
|
returns (uint256)
|
|
519
617
|
{
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
// Get the current surplus amount.
|
|
524
|
-
// If a terminal wasn't provided, use the total surplus across all terminals. Otherwise,
|
|
525
|
-
// get the `terminal`'s surplus.
|
|
526
|
-
uint256 currentSurplus = JBSurplus.currentSurplusOf({
|
|
527
|
-
projectId: projectId,
|
|
528
|
-
terminals: terminals.length != 0 ? terminals : DIRECTORY.terminalsOf(projectId),
|
|
529
|
-
accountingContexts: accountingContexts,
|
|
530
|
-
decimals: decimals,
|
|
531
|
-
currency: currency
|
|
618
|
+
// Aggregate surplus across the terminals, optionally filtered by the specified tokens.
|
|
619
|
+
uint256 currentSurplus = _currentSurplusOf({
|
|
620
|
+
projectId: projectId, terminals: terminals, tokens: tokens, decimals: decimals, currency: currency
|
|
532
621
|
});
|
|
533
622
|
|
|
534
623
|
// If there's no surplus, nothing can be reclaimed.
|
|
@@ -541,12 +630,39 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
541
630
|
// Can't cash out more tokens than are in the total supply.
|
|
542
631
|
if (cashOutCount > totalSupply) return 0;
|
|
543
632
|
|
|
633
|
+
// Get the cash out tax rate from the current ruleset.
|
|
634
|
+
uint256 cashOutTaxRate = RULESETS.currentOf(projectId).cashOutTaxRate();
|
|
635
|
+
|
|
544
636
|
// Return the amount of surplus terminal tokens that would be reclaimed.
|
|
545
637
|
return JBCashOuts.cashOutFrom({
|
|
546
638
|
surplus: currentSurplus,
|
|
547
639
|
cashOutCount: cashOutCount,
|
|
548
640
|
totalSupply: totalSupply,
|
|
549
|
-
cashOutTaxRate:
|
|
641
|
+
cashOutTaxRate: cashOutTaxRate
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/// @notice Gets the current surplus amount for a project across specified terminals and tokens.
|
|
646
|
+
/// @param projectId The ID of the project to get surplus for.
|
|
647
|
+
/// @param terminals The terminals to include. If empty, all project terminals are used.
|
|
648
|
+
/// @param tokens The tokens to include. If empty, all tokens per terminal are used.
|
|
649
|
+
/// @param decimals The number of decimals to expect in the resulting fixed point number.
|
|
650
|
+
/// @param currency The currency the resulting amount should be in terms of.
|
|
651
|
+
/// @return surplus The current surplus amount.
|
|
652
|
+
function currentSurplusOf(
|
|
653
|
+
uint256 projectId,
|
|
654
|
+
IJBTerminal[] calldata terminals,
|
|
655
|
+
address[] calldata tokens,
|
|
656
|
+
uint256 decimals,
|
|
657
|
+
uint256 currency
|
|
658
|
+
)
|
|
659
|
+
external
|
|
660
|
+
view
|
|
661
|
+
override
|
|
662
|
+
returns (uint256)
|
|
663
|
+
{
|
|
664
|
+
return _currentSurplusOf({
|
|
665
|
+
projectId: projectId, terminals: terminals, tokens: tokens, decimals: decimals, currency: currency
|
|
550
666
|
});
|
|
551
667
|
}
|
|
552
668
|
|
|
@@ -572,46 +688,12 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
572
688
|
projectId: projectId,
|
|
573
689
|
cashOutCount: cashOutCount,
|
|
574
690
|
terminals: new IJBTerminal[](0),
|
|
575
|
-
|
|
691
|
+
tokens: new address[](0),
|
|
576
692
|
decimals: decimals,
|
|
577
693
|
currency: currency
|
|
578
694
|
});
|
|
579
695
|
}
|
|
580
696
|
|
|
581
|
-
/// @notice Gets the current surplus amount in a terminal for a specified project.
|
|
582
|
-
/// @dev The surplus is the amount of funds a project has in a terminal in excess of its payout limit.
|
|
583
|
-
/// @dev The surplus is represented as a fixed point number with the same amount of decimals as the specified
|
|
584
|
-
/// terminal.
|
|
585
|
-
/// @param terminal The terminal the surplus is being calculated for.
|
|
586
|
-
/// @param projectId The ID of the project to get surplus for.
|
|
587
|
-
/// @param accountingContexts The accounting contexts of tokens whose balances should contribute to the surplus
|
|
588
|
-
/// being calculated.
|
|
589
|
-
/// @param currency The currency the resulting amount should be in terms of.
|
|
590
|
-
/// @param decimals The number of decimals to expect in the resulting fixed point number.
|
|
591
|
-
/// @return The current surplus amount the project has in the specified terminal.
|
|
592
|
-
function currentSurplusOf(
|
|
593
|
-
address terminal,
|
|
594
|
-
uint256 projectId,
|
|
595
|
-
JBAccountingContext[] calldata accountingContexts,
|
|
596
|
-
uint256 decimals,
|
|
597
|
-
uint256 currency
|
|
598
|
-
)
|
|
599
|
-
external
|
|
600
|
-
view
|
|
601
|
-
override
|
|
602
|
-
returns (uint256)
|
|
603
|
-
{
|
|
604
|
-
// Return the surplus during the project's current ruleset.
|
|
605
|
-
return _surplusFrom({
|
|
606
|
-
terminal: terminal,
|
|
607
|
-
projectId: projectId,
|
|
608
|
-
accountingContexts: accountingContexts,
|
|
609
|
-
ruleset: RULESETS.currentOf(projectId),
|
|
610
|
-
targetDecimals: decimals,
|
|
611
|
-
targetCurrency: currency
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
|
|
615
697
|
/// @notice Gets the current surplus amount for a specified project across all terminals.
|
|
616
698
|
/// @param projectId The ID of the project to get the total surplus for.
|
|
617
699
|
/// @param decimals The number of decimals that the fixed point surplus should include.
|
|
@@ -627,10 +709,10 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
627
709
|
override
|
|
628
710
|
returns (uint256)
|
|
629
711
|
{
|
|
630
|
-
return
|
|
712
|
+
return _currentSurplusOf({
|
|
631
713
|
projectId: projectId,
|
|
632
|
-
terminals:
|
|
633
|
-
|
|
714
|
+
terminals: new IJBTerminal[](0),
|
|
715
|
+
tokens: new address[](0),
|
|
634
716
|
decimals: decimals,
|
|
635
717
|
currency: currency
|
|
636
718
|
});
|
|
@@ -639,11 +721,11 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
639
721
|
/// @notice Simulates a cash out without modifying state.
|
|
640
722
|
/// @dev Invokes data hooks if configured, but skips the balance sufficiency check (balance may change between
|
|
641
723
|
/// preview and execution).
|
|
724
|
+
/// @param terminal The terminal to simulate the cash out from.
|
|
642
725
|
/// @param holder The address cashing out.
|
|
643
726
|
/// @param projectId The ID of the project being cashed out from.
|
|
644
727
|
/// @param cashOutCount The number of project tokens being cashed out.
|
|
645
|
-
/// @param
|
|
646
|
-
/// @param balanceAccountingContexts The accounting contexts to include in the balance calculation.
|
|
728
|
+
/// @param tokenToReclaim The token being reclaimed.
|
|
647
729
|
/// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address.
|
|
648
730
|
/// @param metadata Extra data to pass along to the data hook.
|
|
649
731
|
/// @return ruleset The project's current ruleset.
|
|
@@ -651,11 +733,11 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
651
733
|
/// @return cashOutTaxRate The cash out tax rate that would be applied.
|
|
652
734
|
/// @return hookSpecifications Any cash out hook specifications from the data hook.
|
|
653
735
|
function previewCashOutFrom(
|
|
736
|
+
address terminal,
|
|
654
737
|
address holder,
|
|
655
738
|
uint256 projectId,
|
|
656
739
|
uint256 cashOutCount,
|
|
657
|
-
|
|
658
|
-
JBAccountingContext[] calldata balanceAccountingContexts,
|
|
740
|
+
address tokenToReclaim,
|
|
659
741
|
bool beneficiaryIsFeeless,
|
|
660
742
|
bytes calldata metadata
|
|
661
743
|
)
|
|
@@ -670,12 +752,11 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
670
752
|
)
|
|
671
753
|
{
|
|
672
754
|
(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications) = _computeCashOutFrom({
|
|
673
|
-
terminal:
|
|
755
|
+
terminal: terminal,
|
|
674
756
|
holder: holder,
|
|
675
757
|
projectId: projectId,
|
|
676
758
|
cashOutCount: cashOutCount,
|
|
677
|
-
|
|
678
|
-
balanceAccountingContexts: balanceAccountingContexts,
|
|
759
|
+
tokenToReclaim: tokenToReclaim,
|
|
679
760
|
beneficiaryIsFeeless: beneficiaryIsFeeless,
|
|
680
761
|
metadata: metadata
|
|
681
762
|
});
|
|
@@ -684,6 +765,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
684
765
|
/// @notice Simulates a payment without modifying state.
|
|
685
766
|
/// @dev Invokes data hooks if configured. Returns the same token count and hook specifications that
|
|
686
767
|
/// `recordPaymentFrom` would produce.
|
|
768
|
+
/// @param terminal The terminal to simulate the payment from.
|
|
687
769
|
/// @param payer The address of the payer.
|
|
688
770
|
/// @param amount The amount being paid.
|
|
689
771
|
/// @param projectId The ID of the project being paid.
|
|
@@ -693,6 +775,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
693
775
|
/// @return tokenCount The number of project tokens that would be minted, including reserved tokens.
|
|
694
776
|
/// @return hookSpecifications Any pay hook specifications from the data hook.
|
|
695
777
|
function previewPayFrom(
|
|
778
|
+
address terminal,
|
|
696
779
|
address payer,
|
|
697
780
|
JBTokenAmount memory amount,
|
|
698
781
|
uint256 projectId,
|
|
@@ -704,9 +787,8 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
704
787
|
override
|
|
705
788
|
returns (JBRuleset memory ruleset, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications)
|
|
706
789
|
{
|
|
707
|
-
// Use the caller as the terminal context for the preview.
|
|
708
790
|
(ruleset, tokenCount, hookSpecifications,) = _computePayFrom({
|
|
709
|
-
terminal:
|
|
791
|
+
terminal: terminal,
|
|
710
792
|
payer: payer,
|
|
711
793
|
amount: amount,
|
|
712
794
|
projectId: projectId,
|
|
@@ -719,6 +801,155 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
719
801
|
// -------------------------- internal views ------------------------- //
|
|
720
802
|
//*********************************************************************//
|
|
721
803
|
|
|
804
|
+
/// @notice Computes the surplus relevant for a cash out (total or local, depending on ruleset flag).
|
|
805
|
+
/// @param terminal The terminal the cash out is being recorded from.
|
|
806
|
+
/// @param projectId The ID of the project being cashed out from.
|
|
807
|
+
/// @param tokenToReclaim The token being reclaimed.
|
|
808
|
+
/// @param ruleset The ruleset during the cash out.
|
|
809
|
+
/// @return The surplus amount in the token's native decimals and currency.
|
|
810
|
+
function _cashOutSurplusOf(
|
|
811
|
+
address terminal,
|
|
812
|
+
uint256 projectId,
|
|
813
|
+
address tokenToReclaim,
|
|
814
|
+
JBRuleset memory ruleset
|
|
815
|
+
)
|
|
816
|
+
internal
|
|
817
|
+
view
|
|
818
|
+
returns (uint256)
|
|
819
|
+
{
|
|
820
|
+
// Look up the accounting context (decimals, currency) for the token being reclaimed at this terminal.
|
|
821
|
+
JBAccountingContext memory accountingContext = _accountingContextForTokenOf[terminal][projectId][tokenToReclaim];
|
|
822
|
+
|
|
823
|
+
// If the ruleset uses total surplus, aggregate across ALL terminals and ALL tokens.
|
|
824
|
+
if (ruleset.useTotalSurplusForCashOuts()) {
|
|
825
|
+
return JBSurplus.currentSurplusOf({
|
|
826
|
+
projectId: projectId,
|
|
827
|
+
// Get every terminal the project has registered.
|
|
828
|
+
terminals: DIRECTORY.terminalsOf(projectId),
|
|
829
|
+
// Empty tokens array = include all tokens at each terminal.
|
|
830
|
+
tokens: new address[](0),
|
|
831
|
+
// Express the result in the reclaimed token's decimals and currency.
|
|
832
|
+
decimals: accountingContext.decimals,
|
|
833
|
+
currency: accountingContext.currency
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Otherwise, only account for the specific token's surplus at this terminal.
|
|
838
|
+
JBAccountingContext[] memory singleContext = new JBAccountingContext[](1);
|
|
839
|
+
singleContext[0] = accountingContext;
|
|
840
|
+
|
|
841
|
+
// Compute surplus from only this terminal using only the reclaimed token's balance.
|
|
842
|
+
return _surplusFrom({
|
|
843
|
+
terminal: terminal,
|
|
844
|
+
projectId: projectId,
|
|
845
|
+
accountingContexts: singleContext,
|
|
846
|
+
ruleset: ruleset,
|
|
847
|
+
targetDecimals: accountingContext.decimals,
|
|
848
|
+
targetCurrency: accountingContext.currency
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/// @notice Computes cash out results without writing state.
|
|
853
|
+
/// @param terminal The terminal recording the cash out.
|
|
854
|
+
/// @param holder The account that is cashing out tokens.
|
|
855
|
+
/// @param projectId The ID of the project being cashed out from.
|
|
856
|
+
/// @param cashOutCount The number of project tokens to cash out.
|
|
857
|
+
/// @param tokenToReclaim The token being reclaimed.
|
|
858
|
+
/// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address.
|
|
859
|
+
/// @param metadata Bytes to send to the data hook.
|
|
860
|
+
/// @return ruleset The ruleset during the cash out.
|
|
861
|
+
/// @return reclaimAmount The amount of tokens reclaimed.
|
|
862
|
+
/// @return cashOutTaxRate The cash out tax rate applied.
|
|
863
|
+
/// @return hookSpecifications Cash out hook specifications from the data hook.
|
|
864
|
+
function _computeCashOutFrom(
|
|
865
|
+
address terminal,
|
|
866
|
+
address holder,
|
|
867
|
+
uint256 projectId,
|
|
868
|
+
uint256 cashOutCount,
|
|
869
|
+
address tokenToReclaim,
|
|
870
|
+
bool beneficiaryIsFeeless,
|
|
871
|
+
bytes memory metadata
|
|
872
|
+
)
|
|
873
|
+
internal
|
|
874
|
+
view
|
|
875
|
+
returns (
|
|
876
|
+
JBRuleset memory ruleset,
|
|
877
|
+
uint256 reclaimAmount,
|
|
878
|
+
uint256 cashOutTaxRate,
|
|
879
|
+
JBCashOutHookSpecification[] memory hookSpecifications
|
|
880
|
+
)
|
|
881
|
+
{
|
|
882
|
+
// Get a reference to the project's current ruleset.
|
|
883
|
+
ruleset = RULESETS.currentOf(projectId);
|
|
884
|
+
|
|
885
|
+
// Compute surplus — delegated to keep stack shallow.
|
|
886
|
+
reclaimAmount = _cashOutSurplusOf({
|
|
887
|
+
terminal: terminal, projectId: projectId, tokenToReclaim: tokenToReclaim, ruleset: ruleset
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// Get the accounting context for the token being reclaimed.
|
|
891
|
+
JBAccountingContext memory accountingContext = _accountingContextForTokenOf[terminal][projectId][tokenToReclaim];
|
|
892
|
+
|
|
893
|
+
// Get the total number of outstanding project tokens.
|
|
894
|
+
uint256 totalSupply =
|
|
895
|
+
IJBController(address(DIRECTORY.controllerOf(projectId))).totalTokenSupplyWithReservedTokensOf(projectId);
|
|
896
|
+
|
|
897
|
+
// Can't cash out more tokens than are in the supply.
|
|
898
|
+
if (cashOutCount > totalSupply) revert JBTerminalStore_InsufficientTokens(cashOutCount, totalSupply);
|
|
899
|
+
|
|
900
|
+
// SECURITY NOTE: The data hook has absolute control over cash-out economics.
|
|
901
|
+
// It can set totalSupply, cashOutCount, and cashOutTaxRate to arbitrary values,
|
|
902
|
+
// completely overriding the terminal's bonding curve math. For example, setting
|
|
903
|
+
// totalSupply = surplus makes reclaimAmount = cashOutCount, bypassing the curve.
|
|
904
|
+
// Project owners MUST audit their data hooks with the same rigor as the terminal.
|
|
905
|
+
|
|
906
|
+
// If the ruleset has a data hook which is enabled for cash outs, use it to derive a claim amount and memo.
|
|
907
|
+
if (ruleset.useDataHookForCashOut() && ruleset.dataHook() != address(0)) {
|
|
908
|
+
// Build the cash out context field-by-field to avoid stack-too-deep
|
|
909
|
+
// (the struct has 11 fields — a struct literal would require all values on the stack at once).
|
|
910
|
+
JBBeforeCashOutRecordedContext memory context;
|
|
911
|
+
context.terminal = terminal;
|
|
912
|
+
context.holder = holder;
|
|
913
|
+
context.projectId = projectId;
|
|
914
|
+
context.rulesetId = ruleset.id;
|
|
915
|
+
context.cashOutCount = cashOutCount;
|
|
916
|
+
context.totalSupply = totalSupply;
|
|
917
|
+
context.surplus = JBTokenAmount({
|
|
918
|
+
token: accountingContext.token,
|
|
919
|
+
value: reclaimAmount, // reclaimAmount temporarily holds the current surplus.
|
|
920
|
+
decimals: accountingContext.decimals,
|
|
921
|
+
currency: accountingContext.currency
|
|
922
|
+
});
|
|
923
|
+
context.useTotalSurplus = ruleset.useTotalSurplusForCashOuts();
|
|
924
|
+
context.cashOutTaxRate = ruleset.cashOutTaxRate();
|
|
925
|
+
context.beneficiaryIsFeeless = beneficiaryIsFeeless;
|
|
926
|
+
context.metadata = metadata;
|
|
927
|
+
|
|
928
|
+
(cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications) =
|
|
929
|
+
IJBRulesetDataHook(ruleset.dataHook()).beforeCashOutRecordedWith(context);
|
|
930
|
+
|
|
931
|
+
// Noop specifications are informational only, so they can't also request forwarded funds.
|
|
932
|
+
for (uint256 i; i < hookSpecifications.length; i++) {
|
|
933
|
+
if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
|
|
934
|
+
revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} else {
|
|
938
|
+
cashOutTaxRate = ruleset.cashOutTaxRate();
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Calculate the reclaim amount. `reclaimAmount` currently holds the surplus — overwrite it with the
|
|
942
|
+
// result.
|
|
943
|
+
if (reclaimAmount != 0) {
|
|
944
|
+
reclaimAmount = JBCashOuts.cashOutFrom({
|
|
945
|
+
surplus: reclaimAmount,
|
|
946
|
+
cashOutCount: cashOutCount,
|
|
947
|
+
totalSupply: totalSupply,
|
|
948
|
+
cashOutTaxRate: cashOutTaxRate
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
722
953
|
/// @notice Computes payment results without writing state.
|
|
723
954
|
/// @param terminal The terminal recording the payment.
|
|
724
955
|
/// @param payer The address that made the payment.
|
|
@@ -789,30 +1020,22 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
789
1020
|
// Keep a reference to the amount that should be added to the project's balance.
|
|
790
1021
|
balanceDiff = amount.value;
|
|
791
1022
|
|
|
792
|
-
//
|
|
793
|
-
{
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
// Ensure that the specifications have valid amounts.
|
|
798
|
-
for (uint256 i; i < numberOfSpecifications; i++) {
|
|
799
|
-
// Get a reference to the specification's amount.
|
|
800
|
-
if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
|
|
801
|
-
revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
uint256 specifiedAmount = hookSpecifications[i].amount;
|
|
1023
|
+
// Ensure that the specifications have valid amounts.
|
|
1024
|
+
for (uint256 i; i < hookSpecifications.length; i++) {
|
|
1025
|
+
if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
|
|
1026
|
+
revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
|
|
1027
|
+
}
|
|
805
1028
|
|
|
806
|
-
|
|
807
|
-
if (specifiedAmount != 0) {
|
|
808
|
-
// Can't send more to hook than was paid.
|
|
809
|
-
if (specifiedAmount > balanceDiff) {
|
|
810
|
-
revert JBTerminalStore_InvalidAmountToForwardHook(specifiedAmount, balanceDiff);
|
|
811
|
-
}
|
|
1029
|
+
uint256 specifiedAmount = hookSpecifications[i].amount;
|
|
812
1030
|
|
|
813
|
-
|
|
814
|
-
|
|
1031
|
+
// Can't send more to hook than was paid.
|
|
1032
|
+
if (specifiedAmount != 0) {
|
|
1033
|
+
if (specifiedAmount > balanceDiff) {
|
|
1034
|
+
revert JBTerminalStore_InvalidAmountToForwardHook(specifiedAmount, balanceDiff);
|
|
815
1035
|
}
|
|
1036
|
+
|
|
1037
|
+
// Decrement the total amount being added to the local balance.
|
|
1038
|
+
balanceDiff -= specifiedAmount;
|
|
816
1039
|
}
|
|
817
1040
|
}
|
|
818
1041
|
|
|
@@ -836,124 +1059,59 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
836
1059
|
});
|
|
837
1060
|
|
|
838
1061
|
// Find the number of tokens to mint, as a fixed point number with as many decimals as `weight` has.
|
|
839
|
-
tokenCount = mulDiv(amount.value, weight, weightRatio);
|
|
1062
|
+
tokenCount = mulDiv({x: amount.value, y: weight, denominator: weightRatio});
|
|
840
1063
|
}
|
|
841
1064
|
|
|
842
|
-
/// @notice
|
|
843
|
-
/// @param
|
|
844
|
-
/// @param
|
|
845
|
-
/// @param
|
|
846
|
-
/// @param
|
|
847
|
-
/// @param
|
|
848
|
-
/// @
|
|
849
|
-
|
|
850
|
-
/// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address.
|
|
851
|
-
/// @param metadata Bytes to send to the data hook.
|
|
852
|
-
/// @return ruleset The ruleset during the cash out.
|
|
853
|
-
/// @return reclaimAmount The amount of tokens reclaimed.
|
|
854
|
-
/// @return cashOutTaxRate The cash out tax rate applied.
|
|
855
|
-
/// @return hookSpecifications Cash out hook specifications from the data hook.
|
|
856
|
-
function _computeCashOutFrom(
|
|
857
|
-
address terminal,
|
|
858
|
-
address holder,
|
|
1065
|
+
/// @notice Gets the current surplus amount for a project across specified terminals and tokens.
|
|
1066
|
+
/// @param projectId The ID of the project to get surplus for.
|
|
1067
|
+
/// @param terminals The terminals to include. If empty, all project terminals are used.
|
|
1068
|
+
/// @param tokens The tokens to include. If empty, all tokens per terminal are used.
|
|
1069
|
+
/// @param decimals The number of decimals to expect in the resulting fixed point number.
|
|
1070
|
+
/// @param currency The currency the resulting amount should be in terms of.
|
|
1071
|
+
/// @return surplus The current surplus amount.
|
|
1072
|
+
function _currentSurplusOf(
|
|
859
1073
|
uint256 projectId,
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
bytes memory metadata
|
|
1074
|
+
IJBTerminal[] memory terminals,
|
|
1075
|
+
address[] memory tokens,
|
|
1076
|
+
uint256 decimals,
|
|
1077
|
+
uint256 currency
|
|
865
1078
|
)
|
|
866
1079
|
internal
|
|
867
1080
|
view
|
|
868
|
-
returns (
|
|
869
|
-
JBRuleset memory ruleset,
|
|
870
|
-
uint256 reclaimAmount,
|
|
871
|
-
uint256 cashOutTaxRate,
|
|
872
|
-
JBCashOutHookSpecification[] memory hookSpecifications
|
|
873
|
-
)
|
|
1081
|
+
returns (uint256 surplus)
|
|
874
1082
|
{
|
|
875
|
-
//
|
|
876
|
-
|
|
1083
|
+
// If specific terminals were provided, use them. Otherwise, get all terminals from the directory.
|
|
1084
|
+
IJBTerminal[] memory resolvedTerminals = terminals.length != 0 ? terminals : DIRECTORY.terminalsOf(projectId);
|
|
877
1085
|
|
|
878
|
-
//
|
|
879
|
-
|
|
880
|
-
reclaimAmount = ruleset.useTotalSurplusForCashOuts()
|
|
881
|
-
? JBSurplus.currentSurplusOf({
|
|
882
|
-
projectId: projectId,
|
|
883
|
-
terminals: DIRECTORY.terminalsOf(projectId),
|
|
884
|
-
accountingContexts: new JBAccountingContext[](0),
|
|
885
|
-
decimals: accountingContext.decimals,
|
|
886
|
-
currency: accountingContext.currency
|
|
887
|
-
})
|
|
888
|
-
: _surplusFrom({
|
|
889
|
-
terminal: terminal,
|
|
890
|
-
projectId: projectId,
|
|
891
|
-
accountingContexts: balanceAccountingContexts,
|
|
892
|
-
ruleset: ruleset,
|
|
893
|
-
targetDecimals: accountingContext.decimals,
|
|
894
|
-
targetCurrency: accountingContext.currency
|
|
895
|
-
});
|
|
1086
|
+
// The ruleset determines payout limits, which affect surplus. Fetch it once for all terminals.
|
|
1087
|
+
JBRuleset memory ruleset = RULESETS.currentOf(projectId);
|
|
896
1088
|
|
|
897
|
-
//
|
|
898
|
-
{
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
// completely overriding the terminal's bonding curve math. For example, setting
|
|
909
|
-
// totalSupply = surplus makes reclaimAmount = cashOutCount, bypassing the curve.
|
|
910
|
-
// Project owners MUST audit their data hooks with the same rigor as the terminal.
|
|
911
|
-
|
|
912
|
-
// If the ruleset has a data hook which is enabled for cash outs, use it to derive a claim amount and memo.
|
|
913
|
-
if (ruleset.useDataHookForCashOut() && ruleset.dataHook() != address(0)) {
|
|
914
|
-
// Build the cash out context field-by-field to avoid stack-too-deep
|
|
915
|
-
// (the struct has 11 fields — a struct literal would require all values on the stack at once).
|
|
916
|
-
JBBeforeCashOutRecordedContext memory context;
|
|
917
|
-
context.terminal = terminal;
|
|
918
|
-
context.holder = holder;
|
|
919
|
-
context.projectId = projectId;
|
|
920
|
-
context.rulesetId = ruleset.id;
|
|
921
|
-
context.cashOutCount = cashOutCount;
|
|
922
|
-
context.totalSupply = totalSupply;
|
|
923
|
-
context.surplus = JBTokenAmount({
|
|
924
|
-
token: accountingContext.token,
|
|
925
|
-
value: reclaimAmount, // reclaimAmount temporarily holds the current surplus.
|
|
926
|
-
decimals: accountingContext.decimals,
|
|
927
|
-
currency: accountingContext.currency
|
|
928
|
-
});
|
|
929
|
-
context.useTotalSurplus = ruleset.useTotalSurplusForCashOuts();
|
|
930
|
-
context.cashOutTaxRate = ruleset.cashOutTaxRate();
|
|
931
|
-
context.beneficiaryIsFeeless = beneficiaryIsFeeless;
|
|
932
|
-
context.metadata = metadata;
|
|
933
|
-
|
|
934
|
-
(cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications) =
|
|
935
|
-
IJBRulesetDataHook(ruleset.dataHook()).beforeCashOutRecordedWith(context);
|
|
936
|
-
|
|
937
|
-
// Noop specifications are informational only, so they can't also request forwarded funds.
|
|
938
|
-
for (uint256 i; i < hookSpecifications.length; i++) {
|
|
939
|
-
if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
|
|
940
|
-
revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
|
|
941
|
-
}
|
|
1089
|
+
// Sum surplus across each terminal.
|
|
1090
|
+
for (uint256 i; i < resolvedTerminals.length; i++) {
|
|
1091
|
+
address terminal = address(resolvedTerminals[i]);
|
|
1092
|
+
|
|
1093
|
+
// Build the list of accounting contexts to include in this terminal's surplus calculation.
|
|
1094
|
+
JBAccountingContext[] memory accountingContexts;
|
|
1095
|
+
if (tokens.length != 0) {
|
|
1096
|
+
// Specific tokens requested: look up each token's accounting context at this terminal.
|
|
1097
|
+
accountingContexts = new JBAccountingContext[](tokens.length);
|
|
1098
|
+
for (uint256 j; j < tokens.length; j++) {
|
|
1099
|
+
accountingContexts[j] = _accountingContextForTokenOf[terminal][projectId][tokens[j]];
|
|
942
1100
|
}
|
|
943
1101
|
} else {
|
|
944
|
-
|
|
1102
|
+
// No token filter: use all accounting contexts registered at this terminal.
|
|
1103
|
+
accountingContexts = _accountingContextsOf[terminal][projectId];
|
|
945
1104
|
}
|
|
946
1105
|
|
|
947
|
-
//
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}
|
|
1106
|
+
// Add this terminal's surplus (balance minus payout limits) converted to the target decimals/currency.
|
|
1107
|
+
surplus += _surplusFrom({
|
|
1108
|
+
terminal: terminal,
|
|
1109
|
+
projectId: projectId,
|
|
1110
|
+
accountingContexts: accountingContexts,
|
|
1111
|
+
ruleset: ruleset,
|
|
1112
|
+
targetDecimals: decimals,
|
|
1113
|
+
targetCurrency: currency
|
|
1114
|
+
});
|
|
957
1115
|
}
|
|
958
1116
|
}
|
|
959
1117
|
|
|
@@ -965,7 +1123,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
965
1123
|
/// @param projectId The ID of the project to get the surplus for.
|
|
966
1124
|
/// @param accountingContexts The accounting contexts of tokens whose balances should contribute to the surplus
|
|
967
1125
|
/// being calculated.
|
|
968
|
-
/// @param ruleset The
|
|
1126
|
+
/// @param ruleset The ruleset to base the surplus on.
|
|
969
1127
|
/// @param targetDecimals The number of decimals to include in the resulting fixed point number.
|
|
970
1128
|
/// @param targetCurrency The currency that the reported surplus is expected to be in terms of.
|
|
971
1129
|
/// @return surplus The surplus of funds in terms of `targetCurrency`, as a fixed point number with
|
|
@@ -1038,17 +1196,17 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
1038
1196
|
// Add up all the balances.
|
|
1039
1197
|
surplus = (surplus == 0 || accountingContext.currency == targetCurrency)
|
|
1040
1198
|
? surplus
|
|
1041
|
-
: mulDiv(
|
|
1042
|
-
surplus,
|
|
1043
|
-
10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
1199
|
+
: mulDiv({
|
|
1200
|
+
x: surplus,
|
|
1201
|
+
y: 10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
1044
1202
|
// `_payoutLimitRemaining`'s fidelity as possible when converting.
|
|
1045
|
-
PRICES.pricePerUnitOf({
|
|
1203
|
+
denominator: PRICES.pricePerUnitOf({
|
|
1046
1204
|
projectId: projectId,
|
|
1047
1205
|
pricingCurrency: accountingContext.currency,
|
|
1048
1206
|
unitCurrency: targetCurrency,
|
|
1049
1207
|
decimals: _MAX_FIXED_POINT_FIDELITY
|
|
1050
1208
|
})
|
|
1051
|
-
);
|
|
1209
|
+
});
|
|
1052
1210
|
|
|
1053
1211
|
// Get a reference to the payout limit during the ruleset for the token.
|
|
1054
1212
|
JBCurrencyAmount[] memory payoutLimits = IJBController(address(DIRECTORY.controllerOf(projectId)))
|
|
@@ -1087,17 +1245,17 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
1087
1245
|
|
|
1088
1246
|
// Convert the `payoutLimit`'s amount to be in terms of the provided currency.
|
|
1089
1247
|
if (payoutLimit.amount != 0 && payoutLimit.currency != targetCurrency) {
|
|
1090
|
-
uint256 converted = mulDiv(
|
|
1091
|
-
payoutLimit.amount,
|
|
1092
|
-
10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
1248
|
+
uint256 converted = mulDiv({
|
|
1249
|
+
x: payoutLimit.amount,
|
|
1250
|
+
y: 10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
|
|
1093
1251
|
// `payoutLimitRemaining`'s fidelity as possible when converting.
|
|
1094
|
-
PRICES.pricePerUnitOf({
|
|
1252
|
+
denominator: PRICES.pricePerUnitOf({
|
|
1095
1253
|
projectId: projectId,
|
|
1096
1254
|
pricingCurrency: payoutLimit.currency,
|
|
1097
1255
|
unitCurrency: targetCurrency,
|
|
1098
1256
|
decimals: _MAX_FIXED_POINT_FIDELITY
|
|
1099
1257
|
})
|
|
1100
|
-
);
|
|
1258
|
+
});
|
|
1101
1259
|
if (converted > type(uint224).max) revert JBTerminalStore_Uint224Overflow(converted);
|
|
1102
1260
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1103
1261
|
payoutLimit.amount = uint224(converted);
|