@bananapus/core-v6 0.0.52 → 0.0.53

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/CHANGELOG.md CHANGED
@@ -13,6 +13,17 @@ This file describes the verified change from `nana-core-v5` to the current `nana
13
13
  - `JBTokens`
14
14
  - the shared core interfaces, structs, and libraries under `src/`
15
15
 
16
+ ## 0.0.53 — Drop the `via_ir` requirement
17
+
18
+ `JBCashOutHookSpecsLib.fulfill` originally took 10 named arguments. When `JBMultiTerminal._cashOutTokensOf` and `_payAfterCashOutTokensOf` called it, the call site ran past solc 0.8.28's 16-slot Yul stack ceiling, which forced every consumer of `@bananapus/core-v6` to enable `via_ir = true` in their own `foundry.toml` profile. That cascaded into stack-too-deep failures in downstream packages whose own functions couldn't tolerate `via_ir` (notably `nana-721-hook-v6`'s `JB721TiersHookStore.tiersOf`).
19
+
20
+ This release reshapes `fulfill` to accept the existing `JBAfterCashOutRecordedContext` directly — the same struct the hook receives — so no parallel arg bundle is needed. The new signature is `fulfill(IJBFeelessAddresses, JBAfterCashOutRecordedContext, JBCashOutHookSpecification[])`. The per-iteration loop body is extracted into a private `_fulfillOne` helper so its locals don't share `fulfill`'s stack frame. Both call sites in `JBMultiTerminal` build the context field-by-field (one stack slot per assignment) instead of via a struct literal (which would push all ten fields onto the stack at once and trip the same ceiling at the caller).
21
+
22
+ Integrator impact:
23
+ - `JBCashOutHookSpecsLib.fulfill(IJBFeelessAddresses, uint256, JBTokenAmount, address, uint256, bytes, JBRuleset, uint256, address payable, JBCashOutHookSpecification[])` → `fulfill(IJBFeelessAddresses, JBAfterCashOutRecordedContext, JBCashOutHookSpecification[])`. The only on-chain caller is `JBMultiTerminal` (this PR updates both call sites), so the public ABI surface of `JBMultiTerminal` is unchanged.
24
+ - Consumers of `@bananapus/core-v6@^0.0.53` can drop `via_ir = true` from their `foundry.toml` profiles if they only enabled it because of `JBCashOutHookSpecsLib`. `nana-core-v6`'s own profile flips `via_ir` to `false` to lock in the property.
25
+ - All 997 unit/non-fork tests pass on the refactored library + call sites.
26
+
16
27
  ## Summary
17
28
 
18
29
  - v6 adds explicit preview APIs for pay and cash-out flows. Integrations can simulate more of the terminal path directly from the core contracts.
package/foundry.toml CHANGED
@@ -3,7 +3,7 @@ solc = '0.8.28'
3
3
  bytecode_hash = "none"
4
4
  evm_version = 'cancun'
5
5
  optimizer_runs = 200
6
- via_ir = true
6
+ via_ir = false
7
7
  libs = ["node_modules", "lib"]
8
8
  fs_permissions = [{ access = "read-write", path = "./"}]
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/core-v6",
3
- "version": "0.0.52",
3
+ "version": "0.0.53",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1313,23 +1313,24 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1313
1313
  }
1314
1314
  }
1315
1315
 
1316
- // If the data hook returned cash out hook specifications, fulfill them.
1316
+ // If the data hook returned cash out hook specifications, fulfill them. The context is built
1317
+ // field-by-field rather than as a struct literal so each assignment uses only one stack slot at a
1318
+ // time, keeping the call site under solc 0.8.28's non-via-ir Yul stack ceiling (a 10-field struct
1319
+ // literal would otherwise trip it from inside `_cashOutTokensOf`).
1317
1320
  if (hookSpecifications.length != 0) {
1318
- // Fulfill the cash out hook specifications.
1319
- amountEligibleForFees += JBCashOutHookSpecsLib.fulfill({
1320
- feelessAddresses: FEELESS_ADDRESSES,
1321
- projectId: projectId,
1322
- holder: holder,
1323
- cashOutCount: cashOutCount,
1324
- ruleset: ruleset,
1325
- cashOutTaxRate: cashOutTaxRate,
1326
- beneficiary: beneficiary,
1327
- beneficiaryReclaimAmount: _tokenAmountOf({
1328
- projectId: projectId, token: tokenToReclaim, value: reclaimAmount
1329
- }),
1330
- specifications: hookSpecifications,
1331
- metadata: metadata
1332
- });
1321
+ JBTokenAmount memory reclaimTokenAmount =
1322
+ _tokenAmountOf({projectId: projectId, token: tokenToReclaim, value: reclaimAmount});
1323
+ JBAfterCashOutRecordedContext memory ctx;
1324
+ ctx.holder = holder;
1325
+ ctx.projectId = projectId;
1326
+ ctx.rulesetId = ruleset.id;
1327
+ ctx.cashOutCount = cashOutCount;
1328
+ ctx.reclaimedAmount = reclaimTokenAmount;
1329
+ ctx.forwardedAmount = reclaimTokenAmount;
1330
+ ctx.cashOutTaxRate = cashOutTaxRate;
1331
+ ctx.beneficiary = beneficiary;
1332
+ ctx.cashOutMetadata = metadata;
1333
+ amountEligibleForFees += JBCashOutHookSpecsLib.fulfill(FEELESS_ADDRESSES, ctx, hookSpecifications);
1333
1334
  }
1334
1335
 
1335
1336
  // Cap fee-free surplus at remaining balance.
@@ -1577,20 +1578,19 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1577
1578
  // Hook fees still apply (those funds leave the protocol to external hooks). Hook context sees
1578
1579
  // `address(this)` as the beneficiary since the terminal is custodying the reclaim mid-flow.
1579
1580
  if (hookSpecifications.length != 0) {
1580
- amountEligibleForFees = JBCashOutHookSpecsLib.fulfill({
1581
- feelessAddresses: FEELESS_ADDRESSES,
1582
- projectId: projectId,
1583
- beneficiaryReclaimAmount: _tokenAmountOf({
1584
- projectId: projectId, token: tokenToReclaim, value: reclaimAmount
1585
- }),
1586
- holder: holder,
1587
- cashOutCount: cashOutCount,
1588
- metadata: cashOutMetadata,
1589
- ruleset: ruleset,
1590
- cashOutTaxRate: cashOutTaxRate,
1591
- beneficiary: payable(address(this)),
1592
- specifications: hookSpecifications
1593
- });
1581
+ JBTokenAmount memory reclaimTokenAmount =
1582
+ _tokenAmountOf({projectId: projectId, token: tokenToReclaim, value: reclaimAmount});
1583
+ JBAfterCashOutRecordedContext memory ctx;
1584
+ ctx.holder = holder;
1585
+ ctx.projectId = projectId;
1586
+ ctx.rulesetId = ruleset.id;
1587
+ ctx.cashOutCount = cashOutCount;
1588
+ ctx.reclaimedAmount = reclaimTokenAmount;
1589
+ ctx.forwardedAmount = reclaimTokenAmount;
1590
+ ctx.cashOutTaxRate = cashOutTaxRate;
1591
+ ctx.beneficiary = payable(address(this));
1592
+ ctx.cashOutMetadata = cashOutMetadata;
1593
+ amountEligibleForFees = JBCashOutHookSpecsLib.fulfill(FEELESS_ADDRESSES, ctx, hookSpecifications);
1594
1594
  }
1595
1595
 
1596
1596
  // Cap the source project's fee-free surplus at remaining balance after the outflow. Same invariant as
@@ -8,7 +8,6 @@ import {IJBCashOutHook} from "../interfaces/IJBCashOutHook.sol";
8
8
  import {IJBFeelessAddresses} from "../interfaces/IJBFeelessAddresses.sol";
9
9
  import {JBAfterCashOutRecordedContext} from "../structs/JBAfterCashOutRecordedContext.sol";
10
10
  import {JBCashOutHookSpecification} from "../structs/JBCashOutHookSpecification.sol";
11
- import {JBRuleset} from "../structs/JBRuleset.sol";
12
11
  import {JBTokenAmount} from "../structs/JBTokenAmount.sol";
13
12
  import {JBConstants} from "./JBConstants.sol";
14
13
  import {JBFees} from "./JBFees.sol";
@@ -58,108 +57,104 @@ library JBCashOutHookSpecsLib {
58
57
  /// @dev For each spec: if the hook is feeless, it gets the full spec amount; otherwise the hook gets
59
58
  /// `amount - feeAmountFrom(amount)` and the gross spec amount is added to the eligible-for-fees total
60
59
  /// (the caller takes the fee separately via `_takeFeeFrom`). Cross-token semantics: the hook context's
61
- /// `forwardedAmount` carries the post-fee amount in the same token as `beneficiaryReclaimAmount`.
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`).
62
66
  /// @param feelessAddresses Registry of fee-exempt addresses (consulted per-hook).
63
- /// @param projectId The project being cashed out from.
64
- /// @param beneficiaryReclaimAmount The token amount reference (token, decimals, currency, gross value).
65
- /// @param holder The account whose project tokens were burned.
66
- /// @param cashOutCount The number of project tokens burned.
67
- /// @param metadata Bytes forwarded to each hook as `cashOutMetadata`.
68
- /// @param ruleset The ruleset active during the cash out.
69
- /// @param cashOutTaxRate The cash out tax rate applied.
70
- /// @param beneficiary The address forwarded as the hook context's `beneficiary` (typically the user-supplied
71
- /// recipient or `address(this)` for cross-project flows where the terminal custodies the reclaim mid-flow).
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.
72
70
  /// @param specifications The hook specifications returned by the data hook.
73
71
  /// @return amountEligibleForFees Total spec amounts (gross) from non-feeless hooks, used by the caller to
74
72
  /// charge fees in a single pass.
75
73
  function fulfill(
76
74
  IJBFeelessAddresses feelessAddresses,
77
- uint256 projectId,
78
- JBTokenAmount memory beneficiaryReclaimAmount,
79
- address holder,
80
- uint256 cashOutCount,
81
- bytes memory metadata,
82
- JBRuleset memory ruleset,
83
- uint256 cashOutTaxRate,
84
- address payable beneficiary,
75
+ JBAfterCashOutRecordedContext memory context,
85
76
  JBCashOutHookSpecification[] memory specifications
86
77
  )
87
78
  external
88
79
  returns (uint256 amountEligibleForFees)
89
80
  {
90
- JBAfterCashOutRecordedContext memory context = JBAfterCashOutRecordedContext({
91
- holder: holder,
92
- projectId: projectId,
93
- rulesetId: ruleset.id,
94
- cashOutCount: cashOutCount,
95
- reclaimedAmount: beneficiaryReclaimAmount,
96
- forwardedAmount: beneficiaryReclaimAmount,
97
- cashOutTaxRate: cashOutTaxRate,
98
- beneficiary: beneficiary,
99
- hookMetadata: "",
100
- cashOutMetadata: metadata
101
- });
102
-
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`.
103
83
  for (uint256 i; i < specifications.length;) {
104
- JBCashOutHookSpecification memory specification = specifications[i];
105
-
106
- // A noop specification is informational only and doesn't trigger the hook.
107
- if (specification.noop) {
108
- unchecked {
109
- ++i;
110
- }
111
- continue;
112
- }
113
-
114
- // Get the fee for the specified amount.
115
- uint256 specificationAmountFee = feelessAddresses.isFeelessFor({
116
- addr: address(specification.hook), projectId: projectId
117
- })
118
- ? 0
119
- : JBFees.standardFeeAmountFrom({amountBeforeFee: specification.amount});
120
-
121
- // Add the specification's amount to the amount eligible for fees.
122
- if (specificationAmountFee != 0) {
123
- amountEligibleForFees += 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: beneficiaryReclaimAmount.token,
131
- decimals: beneficiaryReclaimAmount.decimals,
132
- currency: beneficiaryReclaimAmount.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.
139
- // Keep a reference to the amount that'll be paid as a `msg.value`.
140
- uint256 payValue = _beforeTransferTo({
141
- to: address(specification.hook), token: beneficiaryReclaimAmount.token, amount: specification.amount
142
- });
143
-
144
- // Fulfill the specification.
145
- specification.hook.afterCashOutRecordedWith{value: payValue}(context);
146
-
147
- // Revoke the temporary pull allowance now that the hook call has finished.
148
- _afterTransferTo({to: address(specification.hook), token: beneficiaryReclaimAmount.token});
149
-
150
- emit HookAfterRecordCashOut({
151
- hook: specification.hook,
152
- context: context,
153
- specificationAmount: specification.amount,
154
- fee: specificationAmountFee,
155
- caller: msg.sender
156
- });
84
+ amountEligibleForFees += _fulfillOne(feelessAddresses, context, specifications[i]);
157
85
  unchecked {
158
86
  ++i;
159
87
  }
160
88
  }
161
89
  }
162
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
+
163
158
  //*********************************************************************//
164
159
  // ----------------------- private helpers --------------------------- //
165
160
  //*********************************************************************//