@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/src/JBPrices.sol
CHANGED
|
@@ -202,7 +202,7 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
202
202
|
// is in the range of ~1e9 to ~1e27 (for 18 decimals). Extreme prices outside this range may lose
|
|
203
203
|
// significant precision due to fixed-point division truncation.
|
|
204
204
|
if (feed != IJBPriceFeed(address(0))) {
|
|
205
|
-
return mulDiv(10 ** decimals, 10 ** decimals, feed.currentUnitPrice(decimals));
|
|
205
|
+
return mulDiv({x: 10 ** decimals, y: 10 ** decimals, denominator: feed.currentUnitPrice(decimals)});
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// Check for a default feed (project ID 0) if not found.
|
package/src/JBRulesets.sol
CHANGED
|
@@ -623,11 +623,11 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
623
623
|
{
|
|
624
624
|
// A subsequent ruleset to one with a duration of 0 should have the next possible weight.
|
|
625
625
|
if (baseRulesetDuration == 0) {
|
|
626
|
-
return mulDiv(
|
|
627
|
-
baseRulesetWeight,
|
|
628
|
-
JBConstants.MAX_WEIGHT_CUT_PERCENT - baseRulesetWeightCutPercent,
|
|
629
|
-
JBConstants.MAX_WEIGHT_CUT_PERCENT
|
|
630
|
-
);
|
|
626
|
+
return mulDiv({
|
|
627
|
+
x: baseRulesetWeight,
|
|
628
|
+
y: JBConstants.MAX_WEIGHT_CUT_PERCENT - baseRulesetWeightCutPercent,
|
|
629
|
+
denominator: JBConstants.MAX_WEIGHT_CUT_PERCENT
|
|
630
|
+
});
|
|
631
631
|
}
|
|
632
632
|
|
|
633
633
|
// The weight should be based off the base ruleset's weight.
|
|
@@ -673,7 +673,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
673
673
|
|
|
674
674
|
for (uint256 i; i < weightCutMultiple;) {
|
|
675
675
|
// Base the new weight on the specified ruleset's weight.
|
|
676
|
-
weight = mulDiv(weight, cutFactor, maxPercent);
|
|
676
|
+
weight = mulDiv({x: weight, y: cutFactor, denominator: maxPercent});
|
|
677
677
|
|
|
678
678
|
// The calculation doesn't need to continue if the weight is 0.
|
|
679
679
|
if (weight == 0) break;
|
package/src/JBTokens.sol
CHANGED
|
@@ -230,7 +230,7 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
230
230
|
});
|
|
231
231
|
|
|
232
232
|
// Initialize the token.
|
|
233
|
-
token.initialize({name: name, symbol: symbol,
|
|
233
|
+
token.initialize({name: name, symbol: symbol, tokensAddress: address(this)});
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
/// @notice Create new tokens for a holder. If the project has an ERC-20 deployed, tokens are minted directly to
|
|
@@ -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
|
|
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(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
+
import {IJBFeelessHook} from "./IJBFeelessHook.sol";
|
|
5
|
+
|
|
4
6
|
/// @notice Tracks addresses that are exempt from fees, both globally and on a per-project basis.
|
|
5
7
|
/// @dev `projectId = 0` is the wildcard — an address feeless for project 0 is feeless for ALL projects.
|
|
6
8
|
interface IJBFeelessAddresses {
|
|
@@ -11,11 +13,21 @@ interface IJBFeelessAddresses {
|
|
|
11
13
|
/// @param caller The address that set the feeless status.
|
|
12
14
|
event SetFeelessAddress(uint256 indexed projectId, address indexed addr, bool indexed isFeeless, address caller);
|
|
13
15
|
|
|
14
|
-
/// @notice
|
|
15
|
-
///
|
|
16
|
+
/// @notice The optional hook (set by the owner) that can grant feeless status with arbitrary logic. Set to the
|
|
17
|
+
/// zero address to disable.
|
|
18
|
+
/// @param hook The new feeless hook. The zero address disables hook consultation.
|
|
19
|
+
/// @param caller The address that set the hook.
|
|
20
|
+
event SetFeelessHook(IJBFeelessHook indexed hook, address caller);
|
|
21
|
+
|
|
22
|
+
/// @notice The optional hook consulted (in addition to the static mappings) when computing feeless status.
|
|
23
|
+
/// @dev `address(0)` means no hook is set.
|
|
24
|
+
function feelessHook() external view returns (IJBFeelessHook);
|
|
25
|
+
|
|
26
|
+
/// @notice Returns whether the specified address is feeless for a specific project, considering the wildcard
|
|
27
|
+
/// (projectId 0) feeless status, the project-specific feeless status, and the feeless hook (if set).
|
|
16
28
|
/// @param addr The address to check.
|
|
17
29
|
/// @param projectId The ID of the project to check.
|
|
18
|
-
/// @return A flag indicating whether the address is feeless (globally or
|
|
30
|
+
/// @return A flag indicating whether the address is feeless (globally, for the project, or per the hook).
|
|
19
31
|
function isFeelessFor(address addr, uint256 projectId) external view returns (bool);
|
|
20
32
|
|
|
21
33
|
/// @notice Sets whether an address is feeless globally (for all projects).
|
|
@@ -28,4 +40,8 @@ interface IJBFeelessAddresses {
|
|
|
28
40
|
/// @param addr The address to set the feeless status of.
|
|
29
41
|
/// @param flag A flag indicating whether the address should be feeless for the project.
|
|
30
42
|
function setFeelessAddressFor(uint256 projectId, address addr, bool flag) external;
|
|
43
|
+
|
|
44
|
+
/// @notice Sets (or clears) the feeless hook consulted by `isFeelessFor`.
|
|
45
|
+
/// @param hook The new hook. Pass `address(0)` to disable hook consultation.
|
|
46
|
+
function setFeelessHook(IJBFeelessHook hook) external;
|
|
31
47
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
5
|
+
|
|
6
|
+
/// @notice Optional hook that can grant feeless status to addresses based on arbitrary off-chain or on-chain logic.
|
|
7
|
+
/// @dev Plugged into `JBFeelessAddresses` by the contract owner. The hook is OR'd with the static feeless mappings,
|
|
8
|
+
/// so it can only widen the feeless set, never shrink it.
|
|
9
|
+
/// @dev `JBFeelessAddresses` invokes this hook inside a try/catch — a reverting hook is treated as returning `false`,
|
|
10
|
+
/// so a broken hook cannot brick the fee path in terminals.
|
|
11
|
+
interface IJBFeelessHook is IERC165 {
|
|
12
|
+
/// @notice Returns whether the address should be treated as feeless for the project.
|
|
13
|
+
/// @param projectId The ID of the project the fee would be charged on behalf of.
|
|
14
|
+
/// @param addr The address being checked (typically a payout recipient, surplus allowance beneficiary, or
|
|
15
|
+
/// cash-out beneficiary).
|
|
16
|
+
/// @return A flag indicating whether the address is feeless for the project under the hook's custom logic.
|
|
17
|
+
function isFeeless(uint256 projectId, address addr) external view returns (bool);
|
|
18
|
+
}
|
|
@@ -29,8 +29,8 @@ interface IJBToken {
|
|
|
29
29
|
/// @notice Initializes the token with a name, symbol, and the JBTokens contract.
|
|
30
30
|
/// @param name The token's name.
|
|
31
31
|
/// @param symbol The token's symbol.
|
|
32
|
-
/// @param
|
|
33
|
-
function initialize(string memory name, string memory symbol, address
|
|
32
|
+
/// @param tokensAddress The JBTokens contract that manages this token.
|
|
33
|
+
function initialize(string memory name, string memory symbol, address tokensAddress) external;
|
|
34
34
|
|
|
35
35
|
/// @notice Mints tokens to an account.
|
|
36
36
|
/// @param account The address to mint tokens to.
|
|
@@ -42,7 +42,7 @@ library JBCashOuts {
|
|
|
42
42
|
if (cashOutCount >= totalSupply) return surplus;
|
|
43
43
|
|
|
44
44
|
// Get a reference to the linear proportion.
|
|
45
|
-
uint256 base = mulDiv(surplus, cashOutCount, totalSupply);
|
|
45
|
+
uint256 base = mulDiv({x: surplus, y: cashOutCount, denominator: totalSupply});
|
|
46
46
|
|
|
47
47
|
// These conditions are all part of the same curve.
|
|
48
48
|
// Edge conditions are separated to minimize the operations performed in those cases.
|
|
@@ -50,11 +50,12 @@ library JBCashOuts {
|
|
|
50
50
|
return base;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
return mulDiv(
|
|
54
|
-
base,
|
|
55
|
-
(JBConstants.MAX_CASH_OUT_TAX_RATE - cashOutTaxRate)
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
return mulDiv({
|
|
54
|
+
x: base,
|
|
55
|
+
y: (JBConstants.MAX_CASH_OUT_TAX_RATE - cashOutTaxRate)
|
|
56
|
+
+ mulDiv({x: cashOutTaxRate, y: cashOutCount, denominator: totalSupply}),
|
|
57
|
+
denominator: JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
58
|
+
});
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/// @notice Returns the minimum number of tokens that must be cashed out to receive at least `desiredOutput` of
|
|
@@ -93,9 +94,9 @@ library JBCashOuts {
|
|
|
93
94
|
|
|
94
95
|
// Linear case (no tax): out = surplus * c / totalSupply, so c = ceil(out * totalSupply / surplus).
|
|
95
96
|
if (cashOutTaxRate == 0) {
|
|
96
|
-
uint256 count = mulDiv(desiredOutput, totalSupply, surplus);
|
|
97
|
+
uint256 count = mulDiv({x: desiredOutput, y: totalSupply, denominator: surplus});
|
|
97
98
|
// Round up if the floor division undershoots.
|
|
98
|
-
if (mulDiv(surplus, count, totalSupply) < desiredOutput) count++;
|
|
99
|
+
if (mulDiv({x: surplus, y: count, denominator: totalSupply}) < desiredOutput) count++;
|
|
99
100
|
return count;
|
|
100
101
|
}
|
|
101
102
|
|
|
@@ -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 `
|
|
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 `
|
|
25
|
-
uint16 public constant
|
|
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
|
}
|
package/src/libraries/JBFees.sol
CHANGED
|
@@ -3,53 +3,49 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
5
5
|
|
|
6
|
-
import {JBConstants} from "
|
|
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
|
|
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
|
-
return mulDiv(amountBeforeFee, feePercent, JBConstants.MAX_FEE);
|
|
18
|
+
return mulDiv({x: amountBeforeFee, y: feePercent, denominator: JBConstants.MAX_FEE});
|
|
19
|
+
}
|
|
20
|
+
|
|
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({x: amountAfterFee, y: JBConstants.MAX_FEE, denominator: JBConstants.MAX_FEE - feePercent})
|
|
29
|
+
- amountAfterFee;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
/// @notice
|
|
32
|
-
///
|
|
33
|
-
///
|
|
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.
|
|
32
|
+
/// @notice Returns the standard protocol fee taken from `amountBeforeFee`.
|
|
33
|
+
/// @dev The standard fee is `25 / 1_000`, pre-reduced to `1 / 40`. If `JBConstants.STANDARD_FEE` or
|
|
34
|
+
/// `JBConstants.MAX_FEE` changes, this denominator must be reduced again.
|
|
37
35
|
/// @param amountBeforeFee The amount before the fee is applied, as a fixed point number.
|
|
38
36
|
/// @return The fee amount, as a fixed point number with the same number of decimals as `amountBeforeFee`.
|
|
39
37
|
function standardFeeAmountFrom(uint256 amountBeforeFee) internal pure returns (uint256) {
|
|
38
|
+
// `JBConstants.STANDARD_FEE / JBConstants.MAX_FEE` is currently `1 / 40`.
|
|
40
39
|
return amountBeforeFee / 40;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
/// @notice
|
|
44
|
-
///
|
|
45
|
-
///
|
|
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.
|
|
42
|
+
/// @notice Back-calculates the standard protocol fee from a known post-fee amount.
|
|
43
|
+
/// @dev `1 / (1 - 25 / 1_000) - 1` reduces to `40 / 39 - 1`. If `JBConstants.STANDARD_FEE` or
|
|
44
|
+
/// `JBConstants.MAX_FEE` changes, these constants must be reduced again.
|
|
50
45
|
/// @param amountAfterFee The desired post-fee amount, as a fixed point number.
|
|
51
46
|
/// @return The fee amount that, when added to `amountAfterFee`, yields the gross pre-fee amount.
|
|
52
47
|
function standardFeeAmountResultingIn(uint256 amountAfterFee) internal pure returns (uint256) {
|
|
53
|
-
|
|
48
|
+
// Use `mulDiv` instead of `amountAfterFee * 40 / 39` to preserve overflow safety.
|
|
49
|
+
return mulDiv({x: amountAfterFee, y: 40, denominator: 39}) - amountAfterFee;
|
|
54
50
|
}
|
|
55
51
|
}
|
|
@@ -141,7 +141,7 @@ library JBPayoutSplitGroupLib {
|
|
|
141
141
|
// refund = amount * (netPayoutAmount - sent) / netPayoutAmount. For full consumption this branch is
|
|
142
142
|
// skipped. For zero consumption this refunds the full `amount` (i.e. the gross, fee allocation included).
|
|
143
143
|
if (sent < netPayoutAmount) {
|
|
144
|
-
uint256 refund = mulDiv(amount, netPayoutAmount - sent, netPayoutAmount);
|
|
144
|
+
uint256 refund = mulDiv({x: amount, y: netPayoutAmount - sent, denominator: netPayoutAmount});
|
|
145
145
|
if (refund != 0) {
|
|
146
146
|
store.recordAddedBalanceFor({projectId: projectId, token: token, amount: refund});
|
|
147
147
|
}
|
|
@@ -151,7 +151,7 @@ library JBPayoutSplitGroupLib {
|
|
|
151
151
|
// gross-equivalent of what the hook actually consumed so the held fee scales with consumption rather
|
|
152
152
|
// than with the project's original payout intent.
|
|
153
153
|
if (netPayoutAmount < amount && sent != 0) {
|
|
154
|
-
feeEligibleAmount = mulDiv(amount, sent, netPayoutAmount);
|
|
154
|
+
feeEligibleAmount = mulDiv({x: amount, y: sent, denominator: netPayoutAmount});
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -192,7 +192,7 @@ library JBPayoutSplitGroupLib {
|
|
|
192
192
|
JBSplit memory split = payoutSplits[i];
|
|
193
193
|
|
|
194
194
|
// The amount to send to the split.
|
|
195
|
-
uint256 payoutAmount = mulDiv(leftoverAmount, split.percent, leftoverPercentage);
|
|
195
|
+
uint256 payoutAmount = mulDiv({x: leftoverAmount, y: split.percent, denominator: leftoverPercentage});
|
|
196
196
|
|
|
197
197
|
// Send the payout (inlined to keep stack pressure manageable with the tuple return).
|
|
198
198
|
// Returns (netPayoutAmount sent, feeEligible gross-equivalent). For non-hook splits and fully-consumed
|
|
@@ -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 >>
|
|
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 >>
|
|
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
|
+
return address(uint160(ruleset.metadata >> 82));
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
function metadata(JBRuleset memory ruleset) internal pure returns (uint16) {
|
|
91
|
-
|
|
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
|
-
//
|
|
134
|
-
if (rulesetMetadata.
|
|
135
|
-
// use
|
|
136
|
-
if (rulesetMetadata.
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
packed |= uint256(
|
|
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
|
|
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;
|
package/test/helpers/JBTest.sol
CHANGED
|
@@ -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();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {IJBFeelessHook} from "../../src/interfaces/IJBFeelessHook.sol";
|
|
5
|
+
|
|
6
|
+
import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
|
7
|
+
|
|
8
|
+
/// @notice Configurable `IJBFeelessHook` mock for unit tests.
|
|
9
|
+
/// @dev `mode` controls behavior of `isFeeless`:
|
|
10
|
+
/// - 0: returns `defaultResult` for any (projectId, addr)
|
|
11
|
+
/// - 1: reverts with `Nope()`
|
|
12
|
+
/// - 2: reverts via `require(false, "nope")`
|
|
13
|
+
/// - 3: returns `true` iff `(projectId, addr)` has been allowlisted via `setAllowed`
|
|
14
|
+
contract MockFeelessHook is ERC165, IJBFeelessHook {
|
|
15
|
+
error Nope();
|
|
16
|
+
|
|
17
|
+
uint256 public mode;
|
|
18
|
+
bool public defaultResult;
|
|
19
|
+
mapping(uint256 => mapping(address => bool)) internal _allowed;
|
|
20
|
+
|
|
21
|
+
function setMode(uint256 newMode) external {
|
|
22
|
+
mode = newMode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function setDefaultResult(bool result) external {
|
|
26
|
+
defaultResult = result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function setAllowed(uint256 projectId, address addr, bool flag) external {
|
|
30
|
+
_allowed[projectId][addr] = flag;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isFeeless(uint256 projectId, address addr) external view override returns (bool) {
|
|
34
|
+
if (mode == 1) revert Nope();
|
|
35
|
+
if (mode == 2) require(false, "nope");
|
|
36
|
+
if (mode == 3) return _allowed[projectId][addr];
|
|
37
|
+
return defaultResult;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function supportsInterface(bytes4 interfaceId) public view override(IERC165, ERC165) returns (bool) {
|
|
41
|
+
return interfaceId == type(IJBFeelessHook).interfaceId || super.supportsInterface(interfaceId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// @notice Non-conforming "hook" that returns false for the IJBFeelessHook interfaceId in ERC-165.
|
|
46
|
+
/// @dev Used to test the `setFeelessHook` validation guard. Has the `isFeeless` selector so the
|
|
47
|
+
/// call shape matches, but its `supportsInterface` advertises only IERC165, not IJBFeelessHook.
|
|
48
|
+
contract MockNonConformingFeelessHook is ERC165 {
|
|
49
|
+
function isFeeless(uint256, address) external pure returns (bool) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// @notice "Hook" with no `supportsInterface` at all — `setFeelessHook` should revert when this is passed.
|
|
55
|
+
contract MockEoaLikeFeelessHook {
|
|
56
|
+
function isFeeless(uint256, address) external pure returns (bool) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|