@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 +11 -0
- package/foundry.toml +1 -1
- package/package.json +1 -1
- package/src/JBMultiTerminal.sol +30 -30
- package/src/libraries/JBCashOutHookSpecsLib.sol +80 -85
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
package/package.json
CHANGED
package/src/JBMultiTerminal.sol
CHANGED
|
@@ -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
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
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 `
|
|
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
|
|
64
|
-
///
|
|
65
|
-
///
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
//*********************************************************************//
|