@bananapus/core-v6 0.0.53 → 0.0.54

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.
@@ -9,39 +9,6 @@ import {JBRuleset} from "../structs/JBRuleset.sol";
9
9
 
10
10
  /// @notice A terminal that can be cashed out from.
11
11
  interface IJBCashOutTerminal is IJBTerminal {
12
- /// @notice Atomically cash out a holder's tokens of one project and add the reclaim to another project's
13
- /// balance (no project tokens minted on the destination side).
14
- /// @dev Equivalent to calling `cashOutTokensOf` followed by `addToBalanceOf` on the destination project,
15
- /// except the source-side cash out fee is skipped (the equivalent fee is bound on the destination
16
- /// project's side instead). Held-fee return is hardcoded to `false` on the destination side — this
17
- /// entrypoint is for value top-up only, not fee unlock.
18
- /// @dev The destination terminal is whichever terminal the directory has registered as the beneficiary
19
- /// project's primary terminal for `tokenToReclaim` (which may itself be a router that swaps before adding
20
- /// to balance). Cashout-side hooks (if specified by the data hook) execute additively.
21
- /// @dev Round-trip fee preservation is enforced by snapshotting the beneficiary project's
22
- /// accounting-context balances on this terminal before and after the routing, and crediting
23
- /// `_feeFreeSurplusOf` by the per-token delta on each context that grew. The beneficiary project's current
24
- /// ruleset can set `pauseCrossProjectFeeFreeInflows` to opt out.
25
- /// @param holder The address whose project tokens are being burned.
26
- /// @param projectId The ID of the project whose project tokens are being burned.
27
- /// @param cashOutCount The number of project tokens to burn.
28
- /// @param tokenToReclaim The terminal token reclaimed from the source project's surplus.
29
- /// @param beneficiaryProjectId The destination project receiving the reclaim.
30
- /// @param cashOutMetadata Forwarded to the source project's data hook and any cashout hook specifications.
31
- /// @param addToBalanceMetadata Forwarded to the destination project's `addToBalanceOf` event.
32
- /// @return reclaimAmount The gross reclaim amount returned by the store.
33
- function addToBalanceAfterCashOutTokensOf(
34
- address holder,
35
- uint256 projectId,
36
- uint256 cashOutCount,
37
- address tokenToReclaim,
38
- uint256 beneficiaryProjectId,
39
- bytes calldata cashOutMetadata,
40
- bytes calldata addToBalanceMetadata
41
- )
42
- external
43
- returns (uint256 reclaimAmount);
44
-
45
12
  /// @notice A cash out was processed for a project.
46
13
  /// @param rulesetId The ID of the ruleset during the cash out.
47
14
  /// @param rulesetCycleNumber The cycle number of the ruleset during the cash out.
@@ -129,39 +96,4 @@ interface IJBCashOutTerminal is IJBTerminal {
129
96
  )
130
97
  external
131
98
  returns (uint256 reclaimAmount);
132
-
133
- /// @notice Atomically cash out a holder's tokens of one project and pay the reclaim into another. Equivalent
134
- /// to calling `cashOutTokensOf` followed by `pay` on the destination project, except the source-side cash out
135
- /// fee is skipped (the equivalent fee is bound on the destination project's side instead).
136
- /// @dev The destination terminal is whichever terminal the directory has registered as the beneficiary project's
137
- /// primary terminal for `tokenToReclaim` (which may itself be a router that swaps before paying). Cashout-side
138
- /// hooks (if specified by the data hook) execute additively.
139
- /// @dev Round-trip fee preservation is enforced by snapshotting the beneficiary project's accounting-context
140
- /// balances on this terminal before and after the routing, and crediting `_feeFreeSurplusOf` by the per-token
141
- /// delta on each context that grew. The beneficiary project's current ruleset can set
142
- /// `pauseCrossProjectFeeFreeInflows` to opt out.
143
- /// @param holder The address whose project tokens are being burned.
144
- /// @param projectId The ID of the project whose project tokens are being burned.
145
- /// @param cashOutCount The number of project tokens to burn.
146
- /// @param tokenToReclaim The terminal token reclaimed from the source project's surplus.
147
- /// @param beneficiaryProjectId The destination project.
148
- /// @param beneficiary The address that receives the newly minted tokens of the destination project.
149
- /// @param minTokensOut The minimum number of destination-project tokens that must be minted; reverts otherwise.
150
- /// @param cashOutMetadata Forwarded to the source project's data hook and any cashout hook specifications.
151
- /// @param payMetadata Forwarded to the destination project's pay flow.
152
- /// @return reclaimAmount The gross reclaim amount returned by the store.
153
- /// @return beneficiaryTokenCount The number of destination-project tokens minted to `beneficiary`.
154
- function payAfterCashOutTokensOf(
155
- address holder,
156
- uint256 projectId,
157
- uint256 cashOutCount,
158
- address tokenToReclaim,
159
- uint256 beneficiaryProjectId,
160
- address beneficiary,
161
- uint256 minTokensOut,
162
- bytes calldata cashOutMetadata,
163
- bytes calldata payMetadata
164
- )
165
- external
166
- returns (uint256 reclaimAmount, uint256 beneficiaryTokenCount);
167
99
  }
@@ -27,7 +27,7 @@ interface IJBFeeTerminal is IJBTerminal {
27
27
  /// @param projectId The ID of the project the fee was held for.
28
28
  /// @param token The token the fee is denominated in.
29
29
  /// @param amount The amount from which the fee was calculated.
30
- /// @param fee The fee amount held.
30
+ /// @param fee The fee numerator used to calculate the held fee, out of `JBConstants.MAX_FEE`.
31
31
  /// @param beneficiary The address that will receive project tokens when the fee is processed.
32
32
  /// @param caller The address that triggered the fee hold.
33
33
  event HoldFee(
@@ -18,12 +18,9 @@ library JBConstants {
18
18
  /// @notice The denominator for split percentages (9-decimal precision). A split of 1,000,000,000 = 100%.
19
19
  uint32 public constant SPLITS_TOTAL_PERCENT = 1_000_000_000;
20
20
 
21
- /// @notice The fee denominator. The protocol fee is `FEE / MAX_FEE` (currently 25/1000 = 2.5%).
21
+ /// @notice The fee denominator. The protocol fee is `STANDARD_FEE / MAX_FEE`.
22
22
  uint16 public constant MAX_FEE = 1000;
23
23
 
24
- /// @notice The fee numerator. The protocol fee is `FEE / MAX_FEE` = 25/1000 = 2.5%, charged on outflows.
25
- uint16 public constant FEE = 25;
26
-
27
- /// @notice The project ID that receives protocol fees. Should be the first project launched at deployment.
28
- uint256 public constant FEE_BENEFICIARY_PROJECT_ID = 1;
24
+ /// @notice The standard protocol fee numerator. The protocol fee is `STANDARD_FEE / MAX_FEE` = 2.5%.
25
+ uint16 public constant STANDARD_FEE = 25;
29
26
  }
@@ -3,53 +3,48 @@ pragma solidity 0.8.28;
3
3
 
4
4
  import {mulDiv} from "@prb/math/src/Common.sol";
5
5
 
6
- import {JBConstants} from "./../libraries/JBConstants.sol";
6
+ import {JBConstants} from "./JBConstants.sol";
7
7
 
8
8
  /// @notice Fee calculations.
9
9
  library JBFees {
10
- /// @notice Returns the fee amount that, when added to `amountAfterFee`, produces the gross amount needed to yield
11
- /// `amountAfterFee` after the fee is deducted.
12
- /// @dev Use this to back-calculate the fee from a desired post-fee payout.
13
- /// @param amountAfterFee The desired post-fee amount, as a fixed point number.
14
- /// @param feePercent The fee percent, out of `JBConstants.MAX_FEE`.
15
- /// @return The fee amount, as a fixed point number with the same number of decimals as the provided `amount`.
16
- function feeAmountResultingIn(uint256 amountAfterFee, uint256 feePercent) internal pure returns (uint256) {
17
- return mulDiv(amountAfterFee, JBConstants.MAX_FEE, JBConstants.MAX_FEE - feePercent) - amountAfterFee;
18
- }
19
-
20
10
  /// @notice Returns the fee that would be taken from `amountBeforeFee`.
21
11
  /// @dev Use this to forward-calculate the fee from a known pre-fee amount.
22
12
  /// @dev Fee rounding error is bounded by N-1 wei (N = number of splits). Economically
23
13
  /// insignificant. Rounds down (mulDiv floors), so the fee beneficiary may receive up to 1 wei less per split.
24
14
  /// @param amountBeforeFee The amount before the fee is applied, as a fixed point number.
25
15
  /// @param feePercent The fee percent, out of `JBConstants.MAX_FEE`.
26
- /// @return The fee amount, as a fixed point number with the same number of decimals as the provided `amount`.
16
+ /// @return The fee amount, as a fixed point number with the same number of decimals as `amountBeforeFee`.
27
17
  function feeAmountFrom(uint256 amountBeforeFee, uint256 feePercent) internal pure returns (uint256) {
28
18
  return mulDiv(amountBeforeFee, feePercent, JBConstants.MAX_FEE);
29
19
  }
30
20
 
31
- /// @notice Specialization of `feeAmountFrom` that hardcodes the standard protocol fee
32
- /// (`JBConstants.FEE / JBConstants.MAX_FEE` = 25 / 1000 = 1 / 40). Both numerator and denominator are
33
- /// compile-time constants so callers don't need to pass them every call.
34
- /// @dev Pre-reduced: `mulDiv(amount, 25, 1000)` ≡ `amount / 40` (gcd(25, 1000) = 25). Plain integer
35
- /// division is exact (rounds down) and shaves the entire `mulDiv` 512-bit pipeline at every fee site.
36
- /// If `JBConstants.FEE` or `JBConstants.MAX_FEE` changes, the constant `40` here MUST be reduced again.
21
+ /// @notice Returns the fee amount that, when added to `amountAfterFee`, produces the gross amount needed to yield
22
+ /// `amountAfterFee` after the fee is deducted.
23
+ /// @dev Use this to back-calculate the fee from a desired post-fee payout.
24
+ /// @param amountAfterFee The desired post-fee amount, as a fixed point number.
25
+ /// @param feePercent The fee percent, out of `JBConstants.MAX_FEE`.
26
+ /// @return The fee amount, as a fixed point number with the same number of decimals as `amountAfterFee`.
27
+ function feeAmountResultingIn(uint256 amountAfterFee, uint256 feePercent) internal pure returns (uint256) {
28
+ return mulDiv(amountAfterFee, JBConstants.MAX_FEE, JBConstants.MAX_FEE - feePercent) - amountAfterFee;
29
+ }
30
+
31
+ /// @notice Returns the standard protocol fee taken from `amountBeforeFee`.
32
+ /// @dev The standard fee is `25 / 1_000`, pre-reduced to `1 / 40`. If `JBConstants.STANDARD_FEE` or
33
+ /// `JBConstants.MAX_FEE` changes, this denominator must be reduced again.
37
34
  /// @param amountBeforeFee The amount before the fee is applied, as a fixed point number.
38
35
  /// @return The fee amount, as a fixed point number with the same number of decimals as `amountBeforeFee`.
39
36
  function standardFeeAmountFrom(uint256 amountBeforeFee) internal pure returns (uint256) {
37
+ // `JBConstants.STANDARD_FEE / JBConstants.MAX_FEE` is currently `1 / 40`.
40
38
  return amountBeforeFee / 40;
41
39
  }
42
40
 
43
- /// @notice Specialization of `feeAmountResultingIn` that hardcodes the standard protocol fee
44
- /// (`JBConstants.FEE / JBConstants.MAX_FEE`). Use to back-calculate the standard fee from a known
45
- /// post-fee payout.
46
- /// @dev Pre-reduced: `mulDiv(amount, 1000, 1000 - 25) - amount` ≡ `mulDiv(amount, 40, 39) - amount`
47
- /// (gcd(1000, 975) = 25). `mulDiv` (not raw `*`) preserves overflow-safety for the rare wildly-large
48
- /// inputs and matches the rounding behavior of `feeAmountResultingIn` exactly. If `JBConstants.FEE` or
49
- /// `JBConstants.MAX_FEE` changes, the constants `40` and `39` here MUST be reduced again.
41
+ /// @notice Back-calculates the standard protocol fee from a known post-fee amount.
42
+ /// @dev `1 / (1 - 25 / 1_000) - 1` reduces to `40 / 39 - 1`. If `JBConstants.STANDARD_FEE` or
43
+ /// `JBConstants.MAX_FEE` changes, these constants must be reduced again.
50
44
  /// @param amountAfterFee The desired post-fee amount, as a fixed point number.
51
45
  /// @return The fee amount that, when added to `amountAfterFee`, yields the gross pre-fee amount.
52
46
  function standardFeeAmountResultingIn(uint256 amountAfterFee) internal pure returns (uint256) {
47
+ // Use `mulDiv` instead of `amountAfterFee * 40 / 39` to preserve overflow safety.
53
48
  return mulDiv(amountAfterFee, 40, 39) - amountAfterFee;
54
49
  }
55
50
  }
@@ -71,25 +71,20 @@ library JBRulesetMetadataResolver {
71
71
  return ((ruleset.metadata >> 79) & 1) == 1;
72
72
  }
73
73
 
74
- function pauseCrossProjectFeeFreeInflows(JBRuleset memory ruleset) internal pure returns (bool) {
75
- return ((ruleset.metadata >> 80) & 1) == 1;
76
- }
77
-
78
74
  function useDataHookForPay(JBRuleset memory ruleset) internal pure returns (bool) {
79
- return (ruleset.metadata >> 81) & 1 == 1;
75
+ return (ruleset.metadata >> 80) & 1 == 1;
80
76
  }
81
77
 
82
78
  function useDataHookForCashOut(JBRuleset memory ruleset) internal pure returns (bool) {
83
- return (ruleset.metadata >> 82) & 1 == 1;
79
+ return (ruleset.metadata >> 81) & 1 == 1;
84
80
  }
85
81
 
86
82
  function dataHook(JBRuleset memory ruleset) internal pure returns (address) {
87
- return address(uint160(ruleset.metadata >> 83));
83
+ return address(uint160(ruleset.metadata >> 82));
88
84
  }
89
85
 
90
86
  function metadata(JBRuleset memory ruleset) internal pure returns (uint16) {
91
- // 13-bit field at bits 243-255.
92
- return uint16(ruleset.metadata >> 243);
87
+ return uint16(ruleset.metadata >> 242);
93
88
  }
94
89
 
95
90
  /// @notice Pack the funding cycle metadata.
@@ -130,16 +125,14 @@ library JBRulesetMetadataResolver {
130
125
  if (rulesetMetadata.holdFees) packed |= 1 << 78;
131
126
  // scopeCashOutsToLocalBalances in bit 79.
132
127
  if (rulesetMetadata.scopeCashOutsToLocalBalances) packed |= 1 << 79;
133
- // pause cross-project fee-free inflows in bit 80.
134
- if (rulesetMetadata.pauseCrossProjectFeeFreeInflows) packed |= 1 << 80;
135
- // use pay data source in bit 81.
136
- if (rulesetMetadata.useDataHookForPay) packed |= 1 << 81;
137
- // use cash out data source in bit 82.
138
- if (rulesetMetadata.useDataHookForCashOut) packed |= 1 << 82;
139
- // data source address in bits 83-242.
140
- packed |= uint256(uint160(address(rulesetMetadata.dataHook))) << 83;
141
- // metadata in bits 243-255 (13 bits).
142
- packed |= (uint256(rulesetMetadata.metadata) & 0x1FFF) << 243;
128
+ // use pay data source in bit 80.
129
+ if (rulesetMetadata.useDataHookForPay) packed |= 1 << 80;
130
+ // use cash out data source in bit 81.
131
+ if (rulesetMetadata.useDataHookForCashOut) packed |= 1 << 81;
132
+ // data source address in bits 82-241.
133
+ packed |= uint256(uint160(address(rulesetMetadata.dataHook))) << 82;
134
+ // metadata in bits 242-255 (14 bits).
135
+ packed |= (uint256(rulesetMetadata.metadata) & 0x3FFF) << 242;
143
136
  }
144
137
 
145
138
  /// @notice Expand the funding cycle metadata.
@@ -162,7 +155,6 @@ library JBRulesetMetadataResolver {
162
155
  ownerMustSendPayouts: ownerMustSendPayouts(ruleset),
163
156
  holdFees: holdFees(ruleset),
164
157
  scopeCashOutsToLocalBalances: scopeCashOutsToLocalBalances(ruleset),
165
- pauseCrossProjectFeeFreeInflows: pauseCrossProjectFeeFreeInflows(ruleset),
166
158
  useDataHookForPay: useDataHookForPay(ruleset),
167
159
  useDataHookForCashOut: useDataHookForCashOut(ruleset),
168
160
  dataHook: dataHook(ruleset),
@@ -23,12 +23,10 @@ pragma solidity ^0.8.0;
23
23
  /// @custom:member holdFees If `true`, fees are accumulated but not processed until a future ruleset (or manually).
24
24
  /// @custom:member scopeCashOutsToLocalBalances If `true`, omnichain cash-out calculations use only the local chain's
25
25
  /// balances (not cross-chain aggregates).
26
- /// @custom:member pauseCrossProjectFeeFreeInflows If `true`, the project cannot be targeted by
27
- /// `payAfterCashOutTokensOf` calls during this ruleset.
28
26
  /// @custom:member useDataHookForPay If `true`, the data hook is called before recording payments.
29
27
  /// @custom:member useDataHookForCashOut If `true`, the data hook is called before recording cash outs.
30
28
  /// @custom:member dataHook Contract called before pay/cash-out to potentially override token counts or add hooks.
31
- /// @custom:member metadata 13 bits of application-specific metadata (upper 3 bits of the uint16 are ignored).
29
+ /// @custom:member metadata 14 bits of application-specific metadata (upper 2 bits are ignored).
32
30
  struct JBRulesetMetadata {
33
31
  uint16 reservedPercent;
34
32
  uint16 cashOutTaxRate;
@@ -45,7 +43,6 @@ struct JBRulesetMetadata {
45
43
  bool ownerMustSendPayouts;
46
44
  bool holdFees;
47
45
  bool scopeCashOutsToLocalBalances;
48
- bool pauseCrossProjectFeeFreeInflows;
49
46
  bool useDataHookForPay;
50
47
  bool useDataHookForCashOut;
51
48
  address dataHook;
@@ -68,8 +68,7 @@ contract JBTest is Test {
68
68
  useDataHookForPay: false,
69
69
  useDataHookForCashOut: false,
70
70
  dataHook: address(0),
71
- metadata: 0,
72
- pauseCrossProjectFeeFreeInflows: false
71
+ metadata: 0
73
72
  });
74
73
 
75
74
  uint256 packed = _rulesMetadata.packRulesetMetadata();
@@ -107,8 +106,7 @@ contract JBTest is Test {
107
106
  useDataHookForPay: false,
108
107
  useDataHookForCashOut: false,
109
108
  dataHook: address(0),
110
- metadata: 0,
111
- pauseCrossProjectFeeFreeInflows: false
109
+ metadata: 0
112
110
  });
113
111
  }
114
112
 
@@ -132,8 +130,7 @@ contract JBTest is Test {
132
130
  useDataHookForPay: false,
133
131
  useDataHookForCashOut: false,
134
132
  dataHook: address(0),
135
- metadata: 0,
136
- pauseCrossProjectFeeFreeInflows: false
133
+ metadata: 0
137
134
  });
138
135
 
139
136
  uint256 packed = _rulesMetadata.packRulesetMetadata();
@@ -1,176 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
- import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
-
7
- import {IJBCashOutHook} from "../interfaces/IJBCashOutHook.sol";
8
- import {IJBFeelessAddresses} from "../interfaces/IJBFeelessAddresses.sol";
9
- import {JBAfterCashOutRecordedContext} from "../structs/JBAfterCashOutRecordedContext.sol";
10
- import {JBCashOutHookSpecification} from "../structs/JBCashOutHookSpecification.sol";
11
- import {JBTokenAmount} from "../structs/JBTokenAmount.sol";
12
- import {JBConstants} from "./JBConstants.sol";
13
- import {JBFees} from "./JBFees.sol";
14
-
15
- /// @notice Cash-out hook specification fulfillment for `JBMultiTerminal`. Extracted to reduce terminal
16
- /// bytecode size, mirroring the `JBHeldFeesLib` pattern.
17
- /// @dev Called via DELEGATECALL — `address(this)` inside library code is the terminal's address, so token
18
- /// approvals and ETH-bearing hook calls operate on the terminal's balance and allowances. Events are
19
- /// emitted from the terminal address. The `caller` field of `HookAfterRecordCashOut` uses raw `msg.sender`
20
- /// (matching `JBHeldFeesLib`'s precedent) — this means meta-transactions through a trusted forwarder will
21
- /// surface the forwarder address in the event slot, but the actual hook execution semantics are unaffected.
22
- library JBCashOutHookSpecsLib {
23
- // A library that adds default safety checks to ERC20 functionality.
24
- using SafeERC20 for IERC20;
25
-
26
- //*********************************************************************//
27
- // ------------------------------ events ----------------------------- //
28
- //*********************************************************************//
29
-
30
- /// @notice A cash out hook was called after a cash out was recorded.
31
- /// @param hook The cash out hook that was called.
32
- /// @param context The context passed to the hook.
33
- /// @param specificationAmount The amount specified for the hook.
34
- /// @param fee The fee taken from the hook's amount.
35
- /// @param caller The address that called the cash out function.
36
- event HookAfterRecordCashOut(
37
- IJBCashOutHook indexed hook,
38
- JBAfterCashOutRecordedContext context,
39
- uint256 specificationAmount,
40
- uint256 fee,
41
- address caller
42
- );
43
-
44
- //*********************************************************************//
45
- // --------------------------- custom errors ------------------------- //
46
- //*********************************************************************//
47
-
48
- /// @notice Thrown when a hook returns without consuming the full forwarded ERC-20 amount.
49
- error JBMultiTerminal_TemporaryAllowanceNotConsumed(address token, address spender, uint256 allowance);
50
-
51
- //*********************************************************************//
52
- // ----------------------- external functions ------------------------ //
53
- //*********************************************************************//
54
-
55
- /// @notice Iterates `specifications`, calling each non-noop hook with the right ETH/ERC-20 setup, and
56
- /// accumulates the fee-eligible amount across non-feeless hooks.
57
- /// @dev For each spec: if the hook is feeless, it gets the full spec amount; otherwise the hook gets
58
- /// `amount - feeAmountFrom(amount)` and the gross spec amount is added to the eligible-for-fees total
59
- /// (the caller takes the fee separately via `_takeFeeFrom`). Cross-token semantics: the hook context's
60
- /// `forwardedAmount` carries the post-fee amount in the same token as `context.reclaimedAmount`.
61
- /// @dev The caller builds `context` once (`JBAfterCashOutRecordedContext`) and passes it in; the
62
- /// library mutates `context.forwardedAmount` and `context.hookMetadata` per spec. Bundling the
63
- /// caller-side state into the existing hook-context struct keeps the call site under solc 0.8.28's
64
- /// non-via-ir 16-slot Yul stack ceiling (the prior 10-arg shape tripped it from
65
- /// `JBMultiTerminal._cashOutTokensOf`).
66
- /// @param feelessAddresses Registry of fee-exempt addresses (consulted per-hook).
67
- /// @param context Cash-out context to forward into each hook. `context.reclaimedAmount` doubles as the
68
- /// token-amount reference for per-spec transfers; `context.forwardedAmount` / `context.hookMetadata`
69
- /// are rewritten per iteration.
70
- /// @param specifications The hook specifications returned by the data hook.
71
- /// @return amountEligibleForFees Total spec amounts (gross) from non-feeless hooks, used by the caller to
72
- /// charge fees in a single pass.
73
- function fulfill(
74
- IJBFeelessAddresses feelessAddresses,
75
- JBAfterCashOutRecordedContext memory context,
76
- JBCashOutHookSpecification[] memory specifications
77
- )
78
- external
79
- returns (uint256 amountEligibleForFees)
80
- {
81
- // Loop body is extracted into a helper to keep `fulfill`'s stack frame shallow enough for solc 0.8.28
82
- // to compile *without* `via_ir`.
83
- for (uint256 i; i < specifications.length;) {
84
- amountEligibleForFees += _fulfillOne(feelessAddresses, context, specifications[i]);
85
- unchecked {
86
- ++i;
87
- }
88
- }
89
- }
90
-
91
- /// @notice Fulfill a single hook specification: charge the protocol fee (if non-feeless), set up the hook
92
- /// call context, transfer the post-fee amount to the hook, and emit the after-cash-out event.
93
- /// @dev Returns the gross spec amount when the hook is non-feeless (so the caller can accumulate it into
94
- /// `amountEligibleForFees`), or `0` when the hook is feeless / noop. Mutates `context.forwardedAmount`
95
- /// and `context.hookMetadata` so the caller's next iteration can overwrite them.
96
- /// @param feelessAddresses Registry of fee-exempt addresses (consulted to skip the per-hook fee).
97
- /// @param context Cash-out context forwarded into the hook. `context.reclaimedAmount` is used as the
98
- /// token-amount reference for the per-spec transfer; `context.forwardedAmount` and
99
- /// `context.hookMetadata` are overwritten by this call.
100
- /// @param specification The hook specification to fulfill (hook address, amount, metadata, noop flag).
101
- /// @return grossSpecAmount The gross spec amount when the hook is non-feeless (for caller accumulation
102
- /// into `amountEligibleForFees`); `0` for feeless or noop specifications.
103
- function _fulfillOne(
104
- IJBFeelessAddresses feelessAddresses,
105
- JBAfterCashOutRecordedContext memory context,
106
- JBCashOutHookSpecification memory specification
107
- )
108
- private
109
- returns (uint256 grossSpecAmount)
110
- {
111
- // A noop specification is informational only and doesn't trigger the hook.
112
- if (specification.noop) return 0;
113
-
114
- // Get the fee for the specified amount.
115
- uint256 specificationAmountFee = feelessAddresses.isFeelessFor({
116
- addr: address(specification.hook), projectId: context.projectId
117
- })
118
- ? 0
119
- : JBFees.standardFeeAmountFrom({amountBeforeFee: specification.amount});
120
-
121
- // Surface the gross spec amount to the caller so it can be accumulated into `amountEligibleForFees`.
122
- if (specificationAmountFee != 0) {
123
- grossSpecAmount = specification.amount;
124
- specification.amount -= specificationAmountFee;
125
- }
126
-
127
- // Pass the correct token `forwardedAmount` to the hook.
128
- context.forwardedAmount = JBTokenAmount({
129
- value: specification.amount,
130
- token: context.reclaimedAmount.token,
131
- decimals: context.reclaimedAmount.decimals,
132
- currency: context.reclaimedAmount.currency
133
- });
134
-
135
- // Pass the correct metadata from the data hook's specification.
136
- context.hookMetadata = specification.metadata;
137
-
138
- // Trigger any inherited pre-transfer logic. Keep a reference to the amount that'll be paid as a `msg.value`.
139
- uint256 payValue = _beforeTransferTo({
140
- to: address(specification.hook), token: context.reclaimedAmount.token, amount: specification.amount
141
- });
142
-
143
- // Fulfill the specification.
144
- specification.hook.afterCashOutRecordedWith{value: payValue}(context);
145
-
146
- // Revoke the temporary pull allowance now that the hook call has finished.
147
- _afterTransferTo({to: address(specification.hook), token: context.reclaimedAmount.token});
148
-
149
- emit HookAfterRecordCashOut({
150
- hook: specification.hook,
151
- context: context,
152
- specificationAmount: specification.amount,
153
- fee: specificationAmountFee,
154
- caller: msg.sender
155
- });
156
- }
157
-
158
- //*********************************************************************//
159
- // ----------------------- private helpers --------------------------- //
160
- //*********************************************************************//
161
-
162
- /// @notice Native-token transfers use `msg.value` (return the amount). ERC20 transfers grant a temporary
163
- /// pull allowance and return 0 (the recipient is expected to consume it within the same call).
164
- function _beforeTransferTo(address to, address token, uint256 amount) private returns (uint256) {
165
- if (token == JBConstants.NATIVE_TOKEN) return amount;
166
- IERC20(token).forceApprove({spender: to, value: amount});
167
- return 0;
168
- }
169
-
170
- /// @notice Asserts the recipient consumed the temporary ERC20 allowance. No-op for native token.
171
- function _afterTransferTo(address to, address token) private view {
172
- if (token == JBConstants.NATIVE_TOKEN) return;
173
- uint256 allowance = IERC20(token).allowance({owner: address(this), spender: to});
174
- if (allowance != 0) revert JBMultiTerminal_TemporaryAllowanceNotConsumed(token, to, allowance);
175
- }
176
- }