@bananapus/core-v6 0.0.45 → 0.0.47
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/package.json +1 -1
- package/references/types-errors-events.md +3 -3
- package/src/JBController.sol +20 -15
- package/src/JBMultiTerminal.sol +55 -45
- package/src/JBTerminalStore.sol +29 -18
- package/src/interfaces/IJBCashOutTerminal.sol +9 -9
- package/src/interfaces/IJBController.sol +17 -14
- package/src/interfaces/IJBTerminal.sol +8 -8
- package/test/mock/MockMaliciousBeneficiary.sol +3 -7
package/package.json
CHANGED
|
@@ -79,7 +79,7 @@ Use this file when you need deeper protocol reference material after the repo-lo
|
|
|
79
79
|
- `pricePerUnitOf()` is on `IJBPrices`, NOT `IJBController` -- access via `IJBController(ctrl).PRICES().pricePerUnitOf(...)`
|
|
80
80
|
- `JBRulesetConfig` fields need explicit casts: `uint48 mustStartAtOrAfter`, `uint32 duration`, `uint112 weight`, `uint32 weightCutPercent`
|
|
81
81
|
- Zero-amount `pay{value:0}()` and zero-count `cashOutTokensOf(count=0)` are valid no-ops (mint/return 0)
|
|
82
|
-
- `sendPayoutsOf()`
|
|
82
|
+
- `sendPayoutsOf()` auto-caps `amount` to the remaining payout limit -- does NOT revert. Use `minTokensPaidOut` to enforce a minimum.
|
|
83
83
|
- `IJBTokens.claimTokensFor()` takes 4 args: `(holder, projectId, count, beneficiary)` -- NOT 3
|
|
84
84
|
- `JBFeelessAddresses.setFeelessAddress()` NOT `setIsFeelessAddress()` -- the function name omits "Is"
|
|
85
85
|
- Named returns auto-return (no explicit `return` statement needed in Solidity)
|
|
@@ -105,7 +105,7 @@ Use this file when you need deeper protocol reference material after the repo-lo
|
|
|
105
105
|
- `IJBDirectoryAccessControl` has `setControllerAllowed()` and `setTerminalsAllowed()` -- NOT `setControllerAllowedFor()`
|
|
106
106
|
- Price feeds are immutable once set in `JBPrices` -- they cannot be replaced or removed
|
|
107
107
|
- `JBFundAccessLimits` requires payout limits and surplus allowances to be in strictly increasing currency order to prevent duplicates
|
|
108
|
-
- **Empty `fundAccessLimitGroups` = zero payouts, NOT unlimited.** If a ruleset's `fundAccessLimitGroups` array is empty (or has no entry for the terminal/token), `payoutLimitsOf()` returns an empty array → cumulative limit is 0 → `sendPayoutsOf()`
|
|
108
|
+
- **Empty `fundAccessLimitGroups` = zero payouts, NOT unlimited.** If a ruleset's `fundAccessLimitGroups` array is empty (or has no entry for the terminal/token), `payoutLimitsOf()` returns an empty array → cumulative limit is 0 → `sendPayoutsOf()` caps to 0 and returns 0. To allow unlimited payouts, explicitly set a payout limit with `amount: type(uint224).max`.
|
|
109
109
|
- **`groupId` (uint256) vs `currency` (uint32) are different types for the same address.** `JBSplitGroup.groupId` is `uint256(uint160(tokenAddress))` while `JBAccountingContext.currency` is `uint32(uint160(tokenAddress))`. These truncate differently — only `NATIVE_TOKEN` (0x000000000000000000000000000000000000EEEe) matches by coincidence. Don't confuse them.
|
|
110
110
|
- **`JBAccountingContext.currency` is NOT `baseCurrency` — by design.** `baseCurrency` in ruleset metadata uses abstract real-world values (1 = ETH, 2 = USD) so rulesets are portable across chains — `baseCurrency=2` means "issue X tokens per USD" whether on Ethereum, Base, or Arbitrum. `JBAccountingContext.currency` uses token-derived values (`uint32(uint160(tokenAddress))`) because terminals track specific tokens at specific addresses — e.g. NATIVE_TOKEN = 61166, USDC on Ethereum = 909516616, USDC on Base = 3169378579. `JBPrices` mediates between the two: it converts token-derived currencies to/from abstract currencies (e.g. USDC token → USD concept, NATIVE_TOKEN → ETH concept) so that payout limits denominated in USD work correctly regardless of which token the terminal holds. The separation is what makes cross-chain consistency possible: same ruleset, different terminal accounting per chain.
|
|
111
111
|
- **Don't queue multiple identical rulesets.** A ruleset with a `duration` automatically cycles — no need to queue copies. Queue multiple rulesets only when configuration actually changes between periods (e.g. different weight, splits, or limits).
|
|
@@ -166,7 +166,7 @@ Errors an agent is most likely to encounter. All are custom errors (revert with
|
|
|
166
166
|
| `JBMultiTerminal_PermitAllowanceNotEnough` | `JBMultiTerminal` | Permit2 allowance insufficient for the payment amount. |
|
|
167
167
|
| `JBTerminalStore_RulesetPaymentPaused` | `JBTerminalStore` | `pausePay` is set in the current ruleset. |
|
|
168
168
|
| `JBTerminalStore_RulesetNotFound` | `JBTerminalStore` | No ruleset exists for the project (not launched). |
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
| `JBTerminalStore_InadequateControllerAllowance` | `JBTerminalStore` | `useAllowanceOf` amount exceeds the surplus allowance. |
|
|
171
171
|
| `JBTerminalStore_InadequateTerminalStoreBalance` | `JBTerminalStore` | Withdrawal exceeds the terminal's recorded balance. |
|
|
172
172
|
| `JBTerminalStore_InsufficientTokens` | `JBTerminalStore` | Cash out count exceeds the holder's token balance. |
|
package/src/JBController.sol
CHANGED
|
@@ -238,12 +238,12 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
/// @notice Burns a holder's project tokens (or credits), permanently removing them from
|
|
242
|
-
/// during cash outs, or directly by holders who want to burn voluntarily.
|
|
241
|
+
/// @notice Burns a holder's project tokens (or unclaimed credits), permanently removing them from the project
|
|
242
|
+
/// token supply. Used by terminals during cash outs, or directly by holders who want to burn voluntarily.
|
|
243
243
|
/// @dev Can only be called by the holder, an operator with `BURN_TOKENS` permission, or a project terminal.
|
|
244
|
-
/// @param holder The address
|
|
245
|
-
/// @param projectId The ID of the project
|
|
246
|
-
/// @param tokenCount The number of tokens to burn.
|
|
244
|
+
/// @param holder The address whose project tokens (or credits) are being burned.
|
|
245
|
+
/// @param projectId The ID of the project whose tokens are being burned.
|
|
246
|
+
/// @param tokenCount The number of project tokens (or credits) to burn, as a fixed point number with 18 decimals.
|
|
247
247
|
/// @param memo A memo to pass along to the emitted event.
|
|
248
248
|
function burnTokensOf(
|
|
249
249
|
address holder,
|
|
@@ -273,13 +273,16 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
273
273
|
TOKENS.burnFrom({holder: holder, projectId: projectId, count: tokenCount});
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
/// @notice Converts internal credits into the project's ERC-20
|
|
277
|
-
/// wallet. Credits and ERC-20
|
|
276
|
+
/// @notice Converts a holder's internal project token credits into the project's ERC-20 representation,
|
|
277
|
+
/// transferring them to the beneficiary's wallet. Credits and the ERC-20 are equivalent project tokens — this
|
|
278
|
+
/// just
|
|
279
|
+
/// makes them transferable/tradeable.
|
|
278
280
|
/// @dev Can only be called by the credit holder or an operator with `CLAIM_TOKENS` permission.
|
|
279
|
-
/// @param holder The address
|
|
280
|
-
/// @param projectId The ID of the project
|
|
281
|
-
/// @param tokenCount The number of
|
|
282
|
-
///
|
|
281
|
+
/// @param holder The address whose project token credits are being redeemed.
|
|
282
|
+
/// @param projectId The ID of the project whose project tokens are being claimed.
|
|
283
|
+
/// @param tokenCount The number of project token credits to convert into ERC-20 project tokens, as a fixed point
|
|
284
|
+
/// number with 18 decimals.
|
|
285
|
+
/// @param beneficiary The account that receives the resulting ERC-20 project tokens.
|
|
283
286
|
function claimTokensFor(
|
|
284
287
|
address holder,
|
|
285
288
|
uint256 projectId,
|
|
@@ -520,12 +523,14 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
520
523
|
/// reserved percent (which accumulates until `sendReservedTokensToSplitsOf` is called).
|
|
521
524
|
/// @dev Can be called by the project owner, an operator with `MINT_TOKENS` permission, a project terminal, or the
|
|
522
525
|
/// data hook. If `allowOwnerMinting` is false in the current ruleset, only terminals and the data hook can mint.
|
|
523
|
-
/// @param projectId The ID of the project
|
|
524
|
-
/// @param tokenCount The number of tokens to mint
|
|
525
|
-
///
|
|
526
|
+
/// @param projectId The ID of the project whose project tokens are being minted.
|
|
527
|
+
/// @param tokenCount The total number of project tokens to mint (the beneficiary's share plus the reserved share if
|
|
528
|
+
/// `useReservedPercent` is true), as a fixed point number with 18 decimals.
|
|
529
|
+
/// @param beneficiary The address that receives the non-reserved portion of the minted project tokens.
|
|
526
530
|
/// @param memo A memo to pass along to the emitted event.
|
|
527
531
|
/// @param useReservedPercent Whether to apply the ruleset's reserved percent.
|
|
528
|
-
/// @return beneficiaryTokenCount The number of tokens minted
|
|
532
|
+
/// @return beneficiaryTokenCount The number of project tokens minted to `beneficiary` (excluding the reserved
|
|
533
|
+
/// share), as a fixed point number with 18 decimals.
|
|
529
534
|
function mintTokensOf(
|
|
530
535
|
uint256 projectId,
|
|
531
536
|
uint256 tokenCount,
|
package/src/JBMultiTerminal.sol
CHANGED
|
@@ -220,14 +220,15 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
/// @notice Adds funds to a project's balance without minting tokens. Useful for topping
|
|
224
|
-
/// funds. Can also unlock previously held fees by returning them to the project's
|
|
223
|
+
/// @notice Adds funds (terminal tokens) to a project's balance without minting project tokens. Useful for topping
|
|
224
|
+
/// up a project or returning funds. Can also unlock previously held fees by returning them to the project's
|
|
225
|
+
/// balance.
|
|
225
226
|
/// @dev If `shouldReturnHeldFees` is true, the added amount offsets held fees proportionally.
|
|
226
227
|
/// @param projectId The ID of the project to add funds to the balance of.
|
|
227
|
-
/// @param
|
|
228
|
-
///
|
|
229
|
-
/// is
|
|
230
|
-
///
|
|
228
|
+
/// @param token The terminal token being added (the ERC-20, or `JBConstants.NATIVE_TOKEN` for native).
|
|
229
|
+
/// @param amount The amount of the terminal `token` to add, as a fixed point number with the same number of
|
|
230
|
+
/// decimals as the token's accounting context. If `token` is the native token, this argument is ignored and
|
|
231
|
+
/// `msg.value` is used instead.
|
|
231
232
|
/// @param shouldReturnHeldFees If true, return held fees proportional to the amount added.
|
|
232
233
|
/// @param memo A memo to pass along to the emitted event.
|
|
233
234
|
/// @param metadata Extra data to pass along to the emitted event.
|
|
@@ -254,24 +255,25 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
254
255
|
});
|
|
255
256
|
}
|
|
256
257
|
|
|
257
|
-
/// @notice
|
|
258
|
-
/// hook or cash out hook behavior.
|
|
259
|
-
/// @dev Only the token holder or an operator with `CASH_OUT_TOKENS` permission from that holder can call
|
|
260
|
-
///
|
|
258
|
+
/// @notice Burn project tokens to reclaim a share of the project's surplus (held as a terminal token). The
|
|
259
|
+
/// project's current ruleset determines the reclaimed amount, plus any data hook or cash out hook behavior.
|
|
260
|
+
/// @dev Only the project token holder, or an operator with `CASH_OUT_TOKENS` permission from that holder, can call
|
|
261
|
+
/// this.
|
|
262
|
+
/// @dev Two distinct tokens are involved: **project tokens** (`cashOutCount`) are burned from `holder`, and
|
|
263
|
+
/// **terminal tokens** (`tokenToReclaim`) are sent to `beneficiary` in exchange.
|
|
264
|
+
/// @param holder The account whose project tokens are being burned.
|
|
261
265
|
/// @param projectId The ID of the project the project tokens belong to.
|
|
262
|
-
/// @param cashOutCount The number of project tokens to
|
|
263
|
-
///
|
|
264
|
-
/// @param
|
|
265
|
-
///
|
|
266
|
-
///
|
|
267
|
-
/// beneficiary
|
|
268
|
-
/// @param beneficiary The address to send the cashed out terminal tokens to, and to pass along to the ruleset's
|
|
266
|
+
/// @param cashOutCount The number of project tokens to burn, as a fixed point number with 18 decimals.
|
|
267
|
+
/// @param tokenToReclaim The terminal token to reclaim from the project's surplus.
|
|
268
|
+
/// @param minTokensReclaimed The minimum number of terminal tokens that must be returned to `beneficiary` for the
|
|
269
|
+
/// call to succeed, as a fixed point number with the same number of decimals as the terminal token's accounting
|
|
270
|
+
/// context. If fewer terminal tokens would be reclaimed, the cash out is reverted.
|
|
271
|
+
/// @param beneficiary The address to send the reclaimed terminal tokens to, and to pass along to the ruleset's
|
|
269
272
|
/// data hook and cash out hook if applicable.
|
|
270
273
|
/// @param metadata Bytes to send along to the emitted event, as well as the data hook and cash out hook if
|
|
271
274
|
/// applicable.
|
|
272
|
-
/// @return reclaimAmount The amount of terminal tokens
|
|
273
|
-
/// point
|
|
274
|
-
/// number with 18 decimals.
|
|
275
|
+
/// @return reclaimAmount The amount of **terminal tokens** sent to `beneficiary` in exchange for the burned project
|
|
276
|
+
/// tokens, as a fixed point number with the same number of decimals as the terminal token's accounting context.
|
|
275
277
|
function cashOutTokensOf(
|
|
276
278
|
address holder,
|
|
277
279
|
uint256 projectId,
|
|
@@ -565,22 +567,25 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
565
567
|
}
|
|
566
568
|
}
|
|
567
569
|
|
|
568
|
-
/// @notice Pay a project. The project's
|
|
569
|
-
///
|
|
570
|
+
/// @notice Pay a project with a payment token. The project's ruleset determines how many project tokens the
|
|
571
|
+
/// beneficiary receives, plus any data hook or pay hook behavior.
|
|
572
|
+
/// @dev Two distinct tokens are involved: the **payment token** (`token`, e.g. ETH or an ERC-20) flows from the
|
|
573
|
+
/// payer into the terminal, and **project tokens** (the project's own ERC-20 / credit balance) are minted to the
|
|
574
|
+
/// `beneficiary` according to the ruleset's weight.
|
|
570
575
|
/// @param projectId The ID of the project to pay.
|
|
571
|
-
/// @param
|
|
572
|
-
///
|
|
573
|
-
/// is
|
|
574
|
-
///
|
|
575
|
-
/// @param beneficiary The address to mint tokens to, and pass along to the ruleset's data hook and pay
|
|
576
|
-
/// applicable.
|
|
577
|
-
/// @param minReturnedTokens The minimum number of project tokens
|
|
578
|
-
///
|
|
579
|
-
///
|
|
576
|
+
/// @param token The payment token to pay with (the ERC-20, or `JBConstants.NATIVE_TOKEN` for native).
|
|
577
|
+
/// @param amount The amount of the payment `token` to send, as a fixed point number with the same number of
|
|
578
|
+
/// decimals as the token's accounting context. If `token` is the native token, this argument is ignored and
|
|
579
|
+
/// `msg.value` is used in its place.
|
|
580
|
+
/// @param beneficiary The address to mint project tokens to, and to pass along to the ruleset's data hook and pay
|
|
581
|
+
/// hook if applicable.
|
|
582
|
+
/// @param minReturnedTokens The minimum number of project tokens the beneficiary must receive for the payment to
|
|
583
|
+
/// succeed, as a fixed point number with 18 decimals. If fewer project tokens would be minted, the payment is
|
|
584
|
+
/// reverted.
|
|
580
585
|
/// @param memo A memo to pass along to the emitted event.
|
|
581
586
|
/// @param metadata Bytes to pass along to the emitted event, as well as the data hook and pay hook if applicable.
|
|
582
|
-
/// @return beneficiaryTokenCount The number of tokens minted to
|
|
583
|
-
/// decimals.
|
|
587
|
+
/// @return beneficiaryTokenCount The number of **project tokens** minted to `beneficiary`, as a fixed point number
|
|
588
|
+
/// with 18 decimals.
|
|
584
589
|
function pay(
|
|
585
590
|
uint256 projectId,
|
|
586
591
|
address token,
|
|
@@ -728,21 +733,23 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
728
733
|
/// @notice Withdraws funds from a project's surplus (beyond what's needed for payouts) up to the current ruleset's
|
|
729
734
|
/// surplus allowance. Sent directly to a beneficiary address rather than through splits.
|
|
730
735
|
/// @dev Only the project's owner or an operator with `USE_ALLOWANCE` permission can call this.
|
|
731
|
-
/// @dev Incurs the 2.5% protocol fee unless the caller is a feeless address.
|
|
736
|
+
/// @dev Incurs the 2.5% protocol fee unless the caller is a feeless address. The fee is charged in the terminal
|
|
737
|
+
/// token (`token`); the fee project mints **project tokens** in return and sends them to `feeBeneficiary`.
|
|
732
738
|
/// @param projectId The ID of the project to use the surplus allowance of.
|
|
733
|
-
/// @param token The token to pay out from the surplus.
|
|
734
|
-
/// @param amount The amount of terminal
|
|
739
|
+
/// @param token The terminal token to pay out from the surplus.
|
|
740
|
+
/// @param amount The amount of terminal `token` to use from the project's current surplus allowance, as a fixed
|
|
735
741
|
/// point number with the same number of decimals as the token's accounting context.
|
|
736
|
-
/// @param currency The expected currency of
|
|
737
|
-
///
|
|
738
|
-
/// @param minTokensPaidOut The minimum number of terminal tokens that
|
|
739
|
-
/// (excluding fees), as a fixed point number with
|
|
740
|
-
///
|
|
741
|
-
/// @param beneficiary The address to send the
|
|
742
|
-
/// @param feeBeneficiary The address
|
|
742
|
+
/// @param currency The expected currency of `amount`. Must match the currency of one of the project's current
|
|
743
|
+
/// ruleset's surplus allowances.
|
|
744
|
+
/// @param minTokensPaidOut The minimum number of terminal tokens that must be returned from the surplus allowance
|
|
745
|
+
/// (excluding fees), as a fixed point number with the same number of decimals as the terminal token's accounting
|
|
746
|
+
/// context. If less would be paid out, the transaction is reverted.
|
|
747
|
+
/// @param beneficiary The address to send the reclaimed terminal tokens to.
|
|
748
|
+
/// @param feeBeneficiary The address that receives the **project tokens** minted by the fee project in exchange
|
|
749
|
+
/// for the protocol fee paid in terminal tokens.
|
|
743
750
|
/// @param memo A memo to pass along to the emitted event.
|
|
744
|
-
/// @return netAmountPaidOut The number of tokens
|
|
745
|
-
/// the same number of decimals as the token's accounting context.
|
|
751
|
+
/// @return netAmountPaidOut The number of **terminal tokens** sent to `beneficiary`, net of the protocol fee, as a
|
|
752
|
+
/// fixed point number with the same number of decimals as the terminal token's accounting context.
|
|
746
753
|
function useAllowanceOf(
|
|
747
754
|
uint256 projectId,
|
|
748
755
|
address token,
|
|
@@ -1815,6 +1822,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1815
1822
|
(ruleset, amountPaidOut) =
|
|
1816
1823
|
STORE.recordPayoutFor({projectId: projectId, token: token, amount: amount, currency: currency});
|
|
1817
1824
|
|
|
1825
|
+
// If nothing to pay out (e.g. payout limit already used or not configured), return early.
|
|
1826
|
+
if (amountPaidOut == 0) return amountPaidOut;
|
|
1827
|
+
|
|
1818
1828
|
// Get a reference to the project's owner.
|
|
1819
1829
|
// The owner will receive tokens minted by paying the platform fee and receive any leftover funds not sent to
|
|
1820
1830
|
// payout splits.
|
package/src/JBTerminalStore.sol
CHANGED
|
@@ -45,7 +45,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
45
45
|
);
|
|
46
46
|
error JBTerminalStore_AddingAccountingContextNotAllowed(uint256 projectId, uint256 rulesetId, address terminal);
|
|
47
47
|
error JBTerminalStore_InadequateControllerAllowance(uint256 amount, uint256 allowance);
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
error JBTerminalStore_InadequateTerminalStoreBalance(uint256 amount, uint256 balance);
|
|
50
50
|
error JBTerminalStore_InsufficientTokens(uint256 count, uint256 totalSupply);
|
|
51
51
|
error JBTerminalStore_InvalidAmountToForwardHook(uint256 amount, uint256 paidAmount);
|
|
@@ -392,6 +392,26 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
392
392
|
// Get a reference to the project's current ruleset.
|
|
393
393
|
ruleset = RULESETS.currentOf(projectId);
|
|
394
394
|
|
|
395
|
+
// Get the payout limit for this currency.
|
|
396
|
+
uint256 payoutLimit = IJBController(address(DIRECTORY.controllerOf(projectId))).FUND_ACCESS_LIMITS()
|
|
397
|
+
.payoutLimitOf({
|
|
398
|
+
projectId: projectId, rulesetId: ruleset.id, terminal: msg.sender, token: token, currency: currency
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Get the already-used payout limit for this cycle.
|
|
402
|
+
uint256 usedPayoutLimit = usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency];
|
|
403
|
+
|
|
404
|
+
// Cap the amount to the remaining payout limit instead of reverting.
|
|
405
|
+
uint256 remainingPayoutLimit = payoutLimit > usedPayoutLimit ? payoutLimit - usedPayoutLimit : 0;
|
|
406
|
+
if (amount > remainingPayoutLimit) {
|
|
407
|
+
amount = remainingPayoutLimit;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// If nothing can be paid out, return early with zero.
|
|
411
|
+
if (amount == 0) {
|
|
412
|
+
return (ruleset, 0);
|
|
413
|
+
}
|
|
414
|
+
|
|
395
415
|
// Convert the amount to the balance's currency.
|
|
396
416
|
amountPaidOut = (currency == accountingContext.currency)
|
|
397
417
|
? amount
|
|
@@ -407,6 +427,13 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
407
427
|
})
|
|
408
428
|
});
|
|
409
429
|
|
|
430
|
+
// If cross-currency conversion rounded to zero, return without consuming any payout limit. Otherwise a
|
|
431
|
+
// permissionless caller could repeatedly request sub-unit payouts to drain the cycle's payout limit
|
|
432
|
+
// without moving any funds.
|
|
433
|
+
if (amountPaidOut == 0) {
|
|
434
|
+
return (ruleset, 0);
|
|
435
|
+
}
|
|
436
|
+
|
|
410
437
|
// Cache the balance slot to avoid redundant storage reads.
|
|
411
438
|
uint256 currentBalance = balanceOf[msg.sender][projectId][token];
|
|
412
439
|
|
|
@@ -415,29 +442,13 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
415
442
|
revert JBTerminalStore_InadequateTerminalStoreBalance({amount: amountPaidOut, balance: currentBalance});
|
|
416
443
|
}
|
|
417
444
|
|
|
418
|
-
// The new total amount which has been paid out during this ruleset.
|
|
419
|
-
uint256 newUsedPayoutLimitOf =
|
|
420
|
-
usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] + amount;
|
|
421
|
-
|
|
422
|
-
// Amount must be within what is still available to pay out.
|
|
423
|
-
// Validate BEFORE writing to storage to avoid wasting gas on SSTORE when the tx will revert.
|
|
424
|
-
uint256 payoutLimit = IJBController(address(DIRECTORY.controllerOf(projectId))).FUND_ACCESS_LIMITS()
|
|
425
|
-
.payoutLimitOf({
|
|
426
|
-
projectId: projectId, rulesetId: ruleset.id, terminal: msg.sender, token: token, currency: currency
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
// Make sure the new used amount is within the payout limit.
|
|
430
|
-
if (newUsedPayoutLimitOf > payoutLimit || payoutLimit == 0) {
|
|
431
|
-
revert JBTerminalStore_InadequateControllerPayoutLimit({amount: newUsedPayoutLimitOf, limit: payoutLimit});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
445
|
// Removed the paid out funds from the project's token balance.
|
|
435
446
|
unchecked {
|
|
436
447
|
balanceOf[msg.sender][projectId][token] = currentBalance - amountPaidOut;
|
|
437
448
|
}
|
|
438
449
|
|
|
439
450
|
// Store the new used payout limit.
|
|
440
|
-
usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] =
|
|
451
|
+
usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] = usedPayoutLimit + amount;
|
|
441
452
|
}
|
|
442
453
|
|
|
443
454
|
/// @notice Records a terminal migration — zeros out the project's balance and returns the amount moved to
|
|
@@ -75,16 +75,16 @@ interface IJBCashOutTerminal is IJBTerminal {
|
|
|
75
75
|
JBCashOutHookSpecification[] memory hookSpecifications
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
/// @notice
|
|
79
|
-
///
|
|
80
|
-
/// @param holder The address
|
|
81
|
-
/// @param projectId The ID of the project
|
|
82
|
-
/// @param cashOutCount The number of project tokens to
|
|
83
|
-
/// @param tokenToReclaim The token to reclaim from the project's surplus.
|
|
84
|
-
/// @param minTokensReclaimed The minimum number of terminal tokens
|
|
85
|
-
/// @param beneficiary The address to send the reclaimed tokens to.
|
|
78
|
+
/// @notice Burn a holder's project tokens to reclaim a proportional share of the project's surplus (paid out as a
|
|
79
|
+
/// terminal token).
|
|
80
|
+
/// @param holder The address whose project tokens are being burned.
|
|
81
|
+
/// @param projectId The ID of the project whose project tokens are being burned.
|
|
82
|
+
/// @param cashOutCount The number of project tokens to burn.
|
|
83
|
+
/// @param tokenToReclaim The terminal token to reclaim from the project's surplus.
|
|
84
|
+
/// @param minTokensReclaimed The minimum number of terminal tokens that must be reclaimed.
|
|
85
|
+
/// @param beneficiary The address to send the reclaimed terminal tokens to.
|
|
86
86
|
/// @param metadata Extra data to send to the data hook and cash out hooks.
|
|
87
|
-
/// @return reclaimAmount The
|
|
87
|
+
/// @return reclaimAmount The number of terminal tokens reclaimed from the project's surplus.
|
|
88
88
|
function cashOutTokensOf(
|
|
89
89
|
address holder,
|
|
90
90
|
uint256 projectId,
|
|
@@ -261,18 +261,18 @@ interface IJBController is IERC165, IJBProjectUriRegistry, IJBDirectoryAccessCon
|
|
|
261
261
|
)
|
|
262
262
|
external;
|
|
263
263
|
|
|
264
|
-
/// @notice Burns a holder's project tokens or credits.
|
|
265
|
-
/// @param holder The address
|
|
266
|
-
/// @param projectId The ID of the project
|
|
267
|
-
/// @param tokenCount The number of tokens to burn.
|
|
264
|
+
/// @notice Burns a holder's project tokens or unclaimed project token credits, removing them from supply.
|
|
265
|
+
/// @param holder The address whose project tokens (or credits) are being burned.
|
|
266
|
+
/// @param projectId The ID of the project whose project tokens are being burned.
|
|
267
|
+
/// @param tokenCount The number of project tokens (or credits) to burn.
|
|
268
268
|
/// @param memo A memo to pass along to the emitted event.
|
|
269
269
|
function burnTokensOf(address holder, uint256 projectId, uint256 tokenCount, string calldata memo) external;
|
|
270
270
|
|
|
271
|
-
/// @notice
|
|
272
|
-
/// @param holder The address
|
|
273
|
-
/// @param projectId The ID of the project
|
|
274
|
-
/// @param tokenCount The number of
|
|
275
|
-
/// @param beneficiary The account the
|
|
271
|
+
/// @notice Converts project token credits into the project's ERC-20 representation, sending them to a beneficiary.
|
|
272
|
+
/// @param holder The address whose project token credits are being redeemed.
|
|
273
|
+
/// @param projectId The ID of the project whose project tokens are being claimed.
|
|
274
|
+
/// @param tokenCount The number of project token credits to convert into ERC-20 project tokens.
|
|
275
|
+
/// @param beneficiary The account that receives the resulting ERC-20 project tokens.
|
|
276
276
|
function claimTokensFor(address holder, uint256 projectId, uint256 tokenCount, address beneficiary) external;
|
|
277
277
|
|
|
278
278
|
/// @notice Deploys an ERC-20 token for a project.
|
|
@@ -324,13 +324,16 @@ interface IJBController is IERC165, IJBProjectUriRegistry, IJBDirectoryAccessCon
|
|
|
324
324
|
external
|
|
325
325
|
returns (uint256 rulesetId);
|
|
326
326
|
|
|
327
|
-
/// @notice Mints new project tokens or credits to a beneficiary, optionally reserving a portion
|
|
328
|
-
///
|
|
329
|
-
/// @param
|
|
330
|
-
/// @param
|
|
327
|
+
/// @notice Mints new project tokens (or credits) to a beneficiary, optionally reserving a portion for the ruleset's
|
|
328
|
+
/// reserved splits.
|
|
329
|
+
/// @param projectId The ID of the project whose project tokens are being minted.
|
|
330
|
+
/// @param tokenCount The total number of project tokens to mint (the beneficiary's share plus the reserved share if
|
|
331
|
+
/// `useReservedPercent` is true).
|
|
332
|
+
/// @param beneficiary The address that receives the non-reserved portion of the minted project tokens.
|
|
331
333
|
/// @param memo A memo to pass along to the emitted event.
|
|
332
334
|
/// @param useReservedPercent Whether to apply the ruleset's reserved percent.
|
|
333
|
-
/// @return beneficiaryTokenCount The number of tokens minted
|
|
335
|
+
/// @return beneficiaryTokenCount The number of project tokens minted to `beneficiary` (excluding the reserved
|
|
336
|
+
/// share).
|
|
334
337
|
function mintTokensOf(
|
|
335
338
|
uint256 projectId,
|
|
336
339
|
uint256 tokenCount,
|
|
@@ -137,10 +137,10 @@ interface IJBTerminal is IERC165 {
|
|
|
137
137
|
/// @param accountingContexts The accounting contexts to add.
|
|
138
138
|
function addAccountingContextsFor(uint256 projectId, JBAccountingContext[] calldata accountingContexts) external;
|
|
139
139
|
|
|
140
|
-
/// @notice Adds
|
|
140
|
+
/// @notice Adds terminal tokens to a project's balance (no project tokens are minted).
|
|
141
141
|
/// @param projectId The ID of the project to add funds to.
|
|
142
|
-
/// @param token The token added.
|
|
143
|
-
/// @param amount The amount of
|
|
142
|
+
/// @param token The terminal token being added.
|
|
143
|
+
/// @param amount The amount of terminal `token` being added.
|
|
144
144
|
/// @param shouldReturnHeldFees Whether held fees should be returned based on the amount added.
|
|
145
145
|
/// @param memo A memo to pass along to the emitted event.
|
|
146
146
|
/// @param metadata Extra data to pass along to the emitted event.
|
|
@@ -162,15 +162,15 @@ interface IJBTerminal is IERC165 {
|
|
|
162
162
|
/// @return balance The amount of funds that were migrated.
|
|
163
163
|
function migrateBalanceOf(uint256 projectId, address token, IJBTerminal to) external returns (uint256 balance);
|
|
164
164
|
|
|
165
|
-
/// @notice
|
|
165
|
+
/// @notice Pay a project with a payment token in exchange for project tokens minted to a beneficiary.
|
|
166
166
|
/// @param projectId The ID of the project to pay.
|
|
167
|
-
/// @param token The token to pay with.
|
|
168
|
-
/// @param amount The amount of
|
|
167
|
+
/// @param token The payment token to pay with.
|
|
168
|
+
/// @param amount The amount of the payment `token` to pay.
|
|
169
169
|
/// @param beneficiary The address to mint project tokens to.
|
|
170
|
-
/// @param minReturnedTokens The minimum number of project tokens
|
|
170
|
+
/// @param minReturnedTokens The minimum number of project tokens the beneficiary must receive.
|
|
171
171
|
/// @param memo A memo to pass along to the emitted event.
|
|
172
172
|
/// @param metadata Extra data to pass along to the pay hooks.
|
|
173
|
-
/// @return beneficiaryTokenCount The number of tokens minted
|
|
173
|
+
/// @return beneficiaryTokenCount The number of project tokens minted to the beneficiary.
|
|
174
174
|
function pay(
|
|
175
175
|
uint256 projectId,
|
|
176
176
|
address token,
|
|
@@ -11,13 +11,8 @@ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Recei
|
|
|
11
11
|
/// @dev Attempts to re-enter sendPayoutsOf after receiving control-flow.
|
|
12
12
|
contract MaliciousPayoutBeneficiary is IERC721Receiver, Test {
|
|
13
13
|
function reEnter(address _terminal) internal {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 1.5e19, 10 * 10 ** 18
|
|
17
|
-
)
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
IJBMultiTerminal(_terminal)
|
|
14
|
+
// Reentrancy attempt — payout limit already used, so the cap returns 0 (no funds extracted).
|
|
15
|
+
uint256 _reentrantPayout = IJBMultiTerminal(_terminal)
|
|
21
16
|
.sendPayoutsOf({
|
|
22
17
|
projectId: 2,
|
|
23
18
|
amount: 5 * 10 ** 18,
|
|
@@ -25,6 +20,7 @@ contract MaliciousPayoutBeneficiary is IERC721Receiver, Test {
|
|
|
25
20
|
token: JBConstants.NATIVE_TOKEN,
|
|
26
21
|
minTokensPaidOut: 0
|
|
27
22
|
});
|
|
23
|
+
assertEq(_reentrantPayout, 0);
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
receive() external payable {
|