@bananapus/core-v6 0.0.53 → 0.0.55
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 +0 -48
- package/foundry.toml +0 -1
- package/package.json +1 -1
- package/script/helpers/CoreDeploymentLib.sol +2 -3
- package/src/JBController.sol +7 -3
- package/src/JBERC20.sol +7 -7
- package/src/JBFeelessAddresses.sol +50 -5
- package/src/JBMultiTerminal.sol +336 -564
- package/src/JBPrices.sol +1 -1
- package/src/JBRulesets.sol +6 -6
- package/src/JBTokens.sol +1 -1
- package/src/interfaces/IJBCashOutTerminal.sol +0 -68
- package/src/interfaces/IJBFeeTerminal.sol +1 -1
- package/src/interfaces/IJBFeelessAddresses.sol +19 -3
- package/src/interfaces/IJBFeelessHook.sol +18 -0
- package/src/interfaces/IJBToken.sol +2 -2
- package/src/libraries/JBCashOuts.sol +9 -8
- package/src/libraries/JBConstants.sol +3 -6
- package/src/libraries/JBFees.sol +23 -27
- package/src/libraries/JBPayoutSplitGroupLib.sol +3 -3
- package/src/libraries/JBRulesetMetadataResolver.sol +12 -20
- package/src/structs/JBRulesetMetadata.sol +1 -4
- package/test/helpers/JBTest.sol +3 -6
- package/test/mock/MockFeelessHook.sol +59 -0
- package/src/libraries/JBCashOutHookSpecsLib.sol +0 -176
- package/src/libraries/JBHeldFeesLib.sol +0 -288
package/CHANGELOG.md
CHANGED
|
@@ -13,17 +13,6 @@ 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
|
-
|
|
27
16
|
## Summary
|
|
28
17
|
|
|
29
18
|
- v6 adds explicit preview APIs for pay and cash-out flows. Integrations can simulate more of the terminal path directly from the core contracts.
|
|
@@ -41,23 +30,6 @@ Integrator impact:
|
|
|
41
30
|
- `IJBController.addPriceFeed(...)` became `addPriceFeedFor(...)`.
|
|
42
31
|
- `IJBTerminal.currentSurplusOf(...)` now takes `address[] calldata tokens` instead of the old accounting-context array input.
|
|
43
32
|
- The interface surface adds explicit hook-spec return types to preview flows, which changes what off-chain callers can and should decode.
|
|
44
|
-
- `IJBCashOutTerminal.payAfterCashOutTokensOf(...)` is new. It burns source-project tokens and pays the
|
|
45
|
-
reclaim into another project via that project's primary terminal for the reclaim token (which may itself
|
|
46
|
-
be a router that swaps before paying). Source-side cashout fee is skipped; the equivalent fee is bound on
|
|
47
|
-
the destination project's side by crediting `_feeFreeSurplusOf[beneficiaryProjectId][token]` on the first of the destination project's accounting contexts on this
|
|
48
|
-
terminal whose balance grows during the routing. Reverts if no delivery lands on this terminal under any
|
|
49
|
-
of the destination project's accounting contexts.
|
|
50
|
-
- `IJBCashOutTerminal.addToBalanceAfterCashOutTokensOf(...)` is new. Sibling of
|
|
51
|
-
`payAfterCashOutTokensOf` that adds the reclaim to the destination project's balance instead of paying
|
|
52
|
-
(no destination tokens minted). Same source-side fee-skip + destination-side `_feeFreeSurplusOf` credit
|
|
53
|
-
semantics, same opt-out via `pauseCrossProjectFeeFreeInflows`. Held-fee return on the destination side
|
|
54
|
-
is hardcoded to `false` so this entrypoint cannot unlock B's held fees.
|
|
55
|
-
- `JBRulesetMetadata.pauseCrossProjectFeeFreeInflows` is new (bit 80 in the packed metadata word). Opt-out
|
|
56
|
-
flag on the destination project's current ruleset; when set, `payAfterCashOutTokensOf` and
|
|
57
|
-
`addToBalanceAfterCashOutTokensOf` calls targeting the destination project revert. Default
|
|
58
|
-
`false` allows inflows — matching the existing intra-terminal payout semantic where receiving projects
|
|
59
|
-
accumulate `_feeFreeSurplusOf` credits from other projects' outflows. The trailing `metadata` field
|
|
60
|
-
narrowed from 14 to 13 bits to make room.
|
|
61
33
|
|
|
62
34
|
## Breaking ABI changes
|
|
63
35
|
|
|
@@ -66,16 +38,8 @@ Integrator impact:
|
|
|
66
38
|
- `IJBController.previewMintOf(...)` is new.
|
|
67
39
|
- `IJBTerminal.previewPayFor(...)` is new.
|
|
68
40
|
- `IJBCashOutTerminal.previewCashOutFrom(...)` is new.
|
|
69
|
-
- `IJBCashOutTerminal.payAfterCashOutTokensOf(...)` is new.
|
|
70
|
-
- `IJBCashOutTerminal.addToBalanceAfterCashOutTokensOf(...)` is new.
|
|
71
|
-
- `IJBFeeTerminal.FEE()` is REMOVED. The terminal no longer re-exports the protocol fee constant; read
|
|
72
|
-
`JBConstants.FEE` directly. Off-chain integrators that previously called `terminal.FEE()` must switch to
|
|
73
|
-
reading the constant from `JBConstants`.
|
|
74
41
|
- `IJBTerminalStore.previewPayFrom(...)` and `previewCashOutFrom(...)` are new.
|
|
75
42
|
- `IJBTerminal.currentSurplusOf(...)` changed parameter shape.
|
|
76
|
-
- `JBRulesetMetadata` adds `pauseCrossProjectFeeFreeInflows` and narrows `metadata` from 14 to 13 bits.
|
|
77
|
-
Packed `JBRuleset.metadata` layout has shifted accordingly. Integrators that read the packed word directly
|
|
78
|
-
must rebuild against the new layout.
|
|
79
43
|
|
|
80
44
|
## Indexer impact
|
|
81
45
|
|
|
@@ -94,24 +58,12 @@ Integrator impact:
|
|
|
94
58
|
- Added functions
|
|
95
59
|
- `IJBTerminal.previewPayFor(...)`
|
|
96
60
|
- `IJBCashOutTerminal.previewCashOutFrom(...)`
|
|
97
|
-
- `IJBCashOutTerminal.payAfterCashOutTokensOf(...)`
|
|
98
|
-
- `IJBCashOutTerminal.addToBalanceAfterCashOutTokensOf(...)`
|
|
99
61
|
- `IJBTerminalStore.previewPayFrom(...)`
|
|
100
62
|
- `IJBTerminalStore.previewCashOutFrom(...)`
|
|
101
63
|
- `IJBController.previewMintOf(...)`
|
|
102
64
|
- `IJBController.setTokenMetadataOf(...)`
|
|
103
|
-
- Added ruleset metadata flags
|
|
104
|
-
- `JBRulesetMetadata.pauseCrossProjectFeeFreeInflows` (bit 80; narrowed `metadata` field from 14 to 13 bits)
|
|
105
|
-
- Added libraries
|
|
106
|
-
- `JBHeldFeesLib` (held-fee bookkeeping extracted from `JBMultiTerminal` to relieve bytecode budget;
|
|
107
|
-
called via delegatecall, storage refs preserved)
|
|
108
|
-
- `JBCashOutHookSpecsLib` (cash-out hook specification fulfillment extracted from `JBMultiTerminal` to
|
|
109
|
-
relieve bytecode budget after adding `addToBalanceAfterCashOutTokensOf`; called via delegatecall, emits
|
|
110
|
-
`HookAfterRecordCashOut` from the terminal address)
|
|
111
65
|
- Renamed functions
|
|
112
66
|
- `IJBController.addPriceFeed(...)` -> `addPriceFeedFor(...)`
|
|
113
|
-
- Removed functions
|
|
114
|
-
- `IJBFeeTerminal.FEE()` (read `JBConstants.FEE` directly)
|
|
115
67
|
- Changed function shapes
|
|
116
68
|
- `IJBTerminal.currentSurplusOf(...)`
|
|
117
69
|
- Added events
|
package/foundry.toml
CHANGED
package/package.json
CHANGED
|
@@ -43,11 +43,10 @@ library CoreDeploymentLib {
|
|
|
43
43
|
string constant PROJECT_NAME = "nana-core-v6";
|
|
44
44
|
|
|
45
45
|
function getDeployment(string memory path) internal returns (CoreDeployment memory deployment) {
|
|
46
|
-
//
|
|
46
|
+
// Match the current chain ID to the Sphinx network name used in deployment artifacts.
|
|
47
47
|
uint256 chainId = block.chainid;
|
|
48
48
|
|
|
49
|
-
//
|
|
50
|
-
// TODO: get constants without deploy.
|
|
49
|
+
// `SphinxConstants` exposes Sphinx's supported chain ID to network name mapping.
|
|
51
50
|
SphinxConstants sphinxConstants = new SphinxConstants();
|
|
52
51
|
NetworkInfo[] memory networks = sphinxConstants.getNetworkInfoArray();
|
|
53
52
|
|
package/src/JBController.sol
CHANGED
|
@@ -1061,7 +1061,8 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1061
1061
|
JBSplit memory split = splits[i];
|
|
1062
1062
|
|
|
1063
1063
|
// Calculate the amount to send to the split.
|
|
1064
|
-
uint256 splitTokenCount =
|
|
1064
|
+
uint256 splitTokenCount =
|
|
1065
|
+
mulDiv({x: tokenCount, y: split.percent, denominator: JBConstants.SPLITS_TOTAL_PERCENT});
|
|
1065
1066
|
|
|
1066
1067
|
// Mints tokens for the split if needed.
|
|
1067
1068
|
if (splitTokenCount > 0) {
|
|
@@ -1264,8 +1265,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1264
1265
|
returns (uint256 beneficiaryTokenCount, uint256 reservedTokenCount)
|
|
1265
1266
|
{
|
|
1266
1267
|
// Compute the beneficiary's portion after removing the reserved share.
|
|
1267
|
-
beneficiaryTokenCount =
|
|
1268
|
-
|
|
1268
|
+
beneficiaryTokenCount = mulDiv({
|
|
1269
|
+
x: tokenCount,
|
|
1270
|
+
y: JBConstants.MAX_RESERVED_PERCENT - reservedPercent,
|
|
1271
|
+
denominator: JBConstants.MAX_RESERVED_PERCENT
|
|
1272
|
+
});
|
|
1269
1273
|
|
|
1270
1274
|
// The remaining tokens are reserved.
|
|
1271
1275
|
reservedTokenCount = tokenCount - beneficiaryTokenCount;
|
package/src/JBERC20.sol
CHANGED
|
@@ -41,7 +41,7 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
41
41
|
|
|
42
42
|
/// @notice The JBTokens contract that owns this token.
|
|
43
43
|
/// @dev Set via `initialize` because JBERC20 is deployed before JBTokens (circular dependency).
|
|
44
|
-
IJBTokens public
|
|
44
|
+
IJBTokens public tokens;
|
|
45
45
|
|
|
46
46
|
//*********************************************************************//
|
|
47
47
|
// -------------------- private stored properties -------------------- //
|
|
@@ -80,7 +80,7 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
80
80
|
/// @notice Only the JBTokens contract can call this function.
|
|
81
81
|
// forge-lint: disable-next-line(unwrapped-modifier-logic)
|
|
82
82
|
modifier onlyTokens() {
|
|
83
|
-
if (msg.sender != address(
|
|
83
|
+
if (msg.sender != address(tokens)) revert JBERC20_Unauthorized({caller: msg.sender, tokens: address(tokens)});
|
|
84
84
|
_;
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -130,11 +130,11 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
130
130
|
/// @return magicValue `0x1626ba7e` if the signature is valid, `0xffffffff` otherwise.
|
|
131
131
|
function isValidSignature(bytes32 hash, bytes memory signature) external view override returns (bytes4 magicValue) {
|
|
132
132
|
// Recover the signer from the signature. Return invalid if recovery fails.
|
|
133
|
-
(address signer, ECDSA.RecoverError error,) = ECDSA.tryRecover(hash, signature);
|
|
133
|
+
(address signer, ECDSA.RecoverError error,) = ECDSA.tryRecover({hash: hash, signature: signature});
|
|
134
134
|
if (error != ECDSA.RecoverError.NoError) return 0xffffffff;
|
|
135
135
|
|
|
136
136
|
// Get the project ID this token belongs to.
|
|
137
|
-
uint256 projectId =
|
|
137
|
+
uint256 projectId = tokens.projectIdOf(IJBToken(address(this)));
|
|
138
138
|
|
|
139
139
|
// Get the project owner (the NFT holder).
|
|
140
140
|
address projectOwner = PROJECTS.ownerOf(projectId);
|
|
@@ -195,8 +195,8 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
195
195
|
/// @notice Initialize a new project token with the given name, symbol, and owner.
|
|
196
196
|
/// @param name_ The token's name.
|
|
197
197
|
/// @param symbol_ The token's symbol.
|
|
198
|
-
/// @param
|
|
199
|
-
function initialize(string memory name_, string memory symbol_, address
|
|
198
|
+
/// @param tokensAddress The JBTokens contract that manages this token.
|
|
199
|
+
function initialize(string memory name_, string memory symbol_, address tokensAddress) public override {
|
|
200
200
|
// Prevent re-initialization by reverting if a name is already set or if the provided name is empty.
|
|
201
201
|
if (bytes(_name).length != 0 || bytes(name_).length == 0) {
|
|
202
202
|
revert JBERC20_AlreadyInitialized({
|
|
@@ -206,7 +206,7 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
206
206
|
|
|
207
207
|
_name = name_;
|
|
208
208
|
_symbol = symbol_;
|
|
209
|
-
|
|
209
|
+
tokens = IJBTokens(tokensAddress);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
//*********************************************************************//
|
|
@@ -5,12 +5,28 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
5
5
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
6
6
|
|
|
7
7
|
import {IJBFeelessAddresses} from "./interfaces/IJBFeelessAddresses.sol";
|
|
8
|
+
import {IJBFeelessHook} from "./interfaces/IJBFeelessHook.sol";
|
|
8
9
|
|
|
9
10
|
/// @notice A registry of addresses exempt from the protocol's 2.5% fee. Feeless addresses don't incur fees on
|
|
10
11
|
/// payouts they receive, surplus allowance they use, or cash outs where they are the beneficiary.
|
|
11
12
|
/// @dev All feeless status is managed by the contract owner (typically the protocol multisig).
|
|
12
13
|
/// @dev `projectId = 0` is the wildcard — an address feeless for project 0 is feeless for ALL projects.
|
|
13
14
|
contract JBFeelessAddresses is Ownable, IJBFeelessAddresses, IERC165 {
|
|
15
|
+
//*********************************************************************//
|
|
16
|
+
// --------------------------- custom errors ------------------------- //
|
|
17
|
+
//*********************************************************************//
|
|
18
|
+
|
|
19
|
+
error JBFeelessAddresses_InvalidFeelessHook(IJBFeelessHook hook);
|
|
20
|
+
|
|
21
|
+
//*********************************************************************//
|
|
22
|
+
// --------------------- public stored properties -------------------- //
|
|
23
|
+
//*********************************************************************//
|
|
24
|
+
|
|
25
|
+
/// @notice Optional hook consulted (in addition to the static mappings) when computing feeless status.
|
|
26
|
+
/// @dev OR'd with the mappings — the hook can only widen the feeless set, never shrink it. `address(0)` disables
|
|
27
|
+
/// hook consultation.
|
|
28
|
+
IJBFeelessHook public override feelessHook;
|
|
29
|
+
|
|
14
30
|
//*********************************************************************//
|
|
15
31
|
// -------------------- internal stored properties ------------------- //
|
|
16
32
|
//*********************************************************************//
|
|
@@ -53,19 +69,48 @@ contract JBFeelessAddresses is Ownable, IJBFeelessAddresses, IERC165 {
|
|
|
53
69
|
emit SetFeelessAddress({projectId: projectId, addr: addr, isFeeless: flag, caller: _msgSender()});
|
|
54
70
|
}
|
|
55
71
|
|
|
72
|
+
/// @notice Sets (or clears) the feeless hook consulted by `isFeelessFor`.
|
|
73
|
+
/// @dev Can only be called by this contract's owner (typically the protocol multisig).
|
|
74
|
+
/// @dev If `hook` is non-zero, it must report ERC-165 support for `IJBFeelessHook` or this call reverts.
|
|
75
|
+
/// @param hook The new hook. Pass `address(0)` to disable hook consultation.
|
|
76
|
+
function setFeelessHook(IJBFeelessHook hook) external virtual override onlyOwner {
|
|
77
|
+
if (address(hook) != address(0) && !hook.supportsInterface(type(IJBFeelessHook).interfaceId)) {
|
|
78
|
+
revert JBFeelessAddresses_InvalidFeelessHook(hook);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
feelessHook = hook;
|
|
82
|
+
|
|
83
|
+
emit SetFeelessHook({hook: hook, caller: _msgSender()});
|
|
84
|
+
}
|
|
85
|
+
|
|
56
86
|
//*********************************************************************//
|
|
57
|
-
//
|
|
87
|
+
// ------------------------- external views -------------------------- //
|
|
58
88
|
//*********************************************************************//
|
|
59
89
|
|
|
60
|
-
/// @notice Returns whether the specified address is feeless for a specific project, considering
|
|
61
|
-
/// (projectId 0)
|
|
90
|
+
/// @notice Returns whether the specified address is feeless for a specific project, considering the wildcard
|
|
91
|
+
/// (projectId 0) feeless status, the project-specific feeless status, and the feeless hook (if set).
|
|
92
|
+
/// @dev The hook is invoked via try/catch — a reverting or out-of-gas hook is treated as `false` so it cannot
|
|
93
|
+
/// brick the fee path in terminals.
|
|
62
94
|
/// @param addr The address to check.
|
|
63
95
|
/// @param projectId The ID of the project to check.
|
|
64
|
-
/// @return A flag indicating whether the address is feeless (globally or
|
|
96
|
+
/// @return A flag indicating whether the address is feeless (globally, for the project, or per the hook).
|
|
65
97
|
function isFeelessFor(address addr, uint256 projectId) external view override returns (bool) {
|
|
66
|
-
|
|
98
|
+
if (_isFeelessFor[0][addr] || _isFeelessFor[projectId][addr]) return true;
|
|
99
|
+
|
|
100
|
+
IJBFeelessHook hook = feelessHook;
|
|
101
|
+
if (address(hook) == address(0)) return false;
|
|
102
|
+
|
|
103
|
+
try hook.isFeeless({projectId: projectId, addr: addr}) returns (bool result) {
|
|
104
|
+
return result;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
67
108
|
}
|
|
68
109
|
|
|
110
|
+
//*********************************************************************//
|
|
111
|
+
// -------------------------- public views --------------------------- //
|
|
112
|
+
//*********************************************************************//
|
|
113
|
+
|
|
69
114
|
/// @notice Indicates whether this contract adheres to the specified interface.
|
|
70
115
|
/// @dev See {IERC165-supportsInterface}.
|
|
71
116
|
/// @param interfaceId The ID of the interface to check for adherence to.
|