@bananapus/core-v6 0.0.61 → 0.0.63

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/core-v6",
3
- "version": "0.0.61",
3
+ "version": "0.0.63",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -97,8 +97,11 @@ library CoreDeploymentLib {
97
97
  })
98
98
  );
99
99
 
100
+ // Controller is loaded best-effort so the periphery deploy script can bootstrap on a fresh chain that
101
+ // hasn't yet produced `JBController.json` — that script is the one that creates the controller. Other
102
+ // core artifacts are still required.
100
103
  deployment.controller = JBController(
101
- _getDeploymentAddress({
104
+ _tryGetDeploymentAddress({
102
105
  path: path, projectName: PROJECT_NAME, networkName: networkName, contractName: "JBController"
103
106
  })
104
107
  );
@@ -164,4 +167,24 @@ library CoreDeploymentLib {
164
167
  vm.readFile(string.concat(path, projectName, "/", networkName, "/", contractName, ".json"));
165
168
  return stdJson.readAddress({json: deploymentJson, key: ".address"});
166
169
  }
170
+
171
+ /// @notice Best-effort variant of `_getDeploymentAddress`. Returns `address(0)` when the artifact file does
172
+ /// not exist on disk, rather than reverting. Used for fields that may legitimately be absent during a
173
+ /// fresh-chain bootstrap (e.g. the controller artifact when the periphery script is the one creating it).
174
+ function _tryGetDeploymentAddress(
175
+ string memory path,
176
+ string memory projectName,
177
+ string memory networkName,
178
+ string memory contractName
179
+ )
180
+ internal
181
+ view
182
+ returns (address)
183
+ {
184
+ string memory filePath = string.concat(path, projectName, "/", networkName, "/", contractName, ".json");
185
+ // forge-lint: disable-next-line(unsafe-cheatcode)
186
+ if (!vm.exists(filePath)) return address(0);
187
+ // forge-lint: disable-next-line(unsafe-cheatcode)
188
+ return stdJson.readAddress({json: vm.readFile(filePath), key: ".address"});
189
+ }
167
190
  }
@@ -172,8 +172,10 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
172
172
  //*********************************************************************//
173
173
 
174
174
  /// @notice Registers a price feed so the project can use a new currency for payout limits or surplus allowances.
175
- /// @dev Can only be called by the project's owner or an operator with `ADD_PRICE_FEED` permission. The current
176
- /// ruleset must have `allowAddPriceFeed` enabled.
175
+ /// @dev Can only be called by the project's owner or an operator with `ADD_PRICE_FEED` permission. When a current
176
+ /// ruleset exists, it must have `allowAddPriceFeed` enabled. If no current ruleset exists (`ruleset.id == 0`),
177
+ /// the call is allowed unconditionally — this is the symmetric counterpart to `setTokenFor`, which mirrors the
178
+ /// gap-state allowance so projects can configure prerequisites before their first ruleset activates.
177
179
  /// @param projectId The ID of the project to add the feed for.
178
180
  /// @param pricingCurrency The currency the feed's output price is in terms of.
179
181
  /// @param unitCurrency The currency the feed prices.
@@ -194,8 +196,12 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
194
196
 
195
197
  JBRuleset memory ruleset = _currentRulesetOf(projectId);
196
198
 
197
- // Make sure the project's ruleset allows adding price feeds.
198
- if (!ruleset.allowAddPriceFeed()) revert JBController_AddingPriceFeedNotAllowed(projectId);
199
+ // When a current ruleset exists, it must allow adding price feeds. If no current ruleset exists
200
+ // (`ruleset.id == 0`), the call is allowed unconditionally so projects can register feeds during the
201
+ // pre-launch or gap state — matching the gap-state semantics of `setTokenFor`.
202
+ if (ruleset.id != 0 && !ruleset.allowAddPriceFeed()) {
203
+ revert JBController_AddingPriceFeedNotAllowed(projectId);
204
+ }
199
205
 
200
206
  PRICES.addPriceFeedFor({
201
207
  projectId: projectId, pricingCurrency: pricingCurrency, unitCurrency: unitCurrency, feed: feed
@@ -1102,7 +1108,9 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
1102
1108
  })
1103
1109
  ) {}
1104
1110
  catch (bytes memory reason) {
1105
- emit SplitHookReverted({projectId: projectId, hook: address(split.hook), reason: reason});
1111
+ emit SplitHookReverted({
1112
+ projectId: projectId, hook: address(split.hook), reason: reason, caller: messageSender
1113
+ });
1106
1114
  }
1107
1115
 
1108
1116
  // For ERC-20 tokens, burn any tokens the hook did not consume.
@@ -501,8 +501,11 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
501
501
  /// @param token The token the fee is paid in.
502
502
  /// @param amount The fee amount, as a fixed point number with the same number of decimals as the token's
503
503
  /// accounting context.
504
- /// @param beneficiary The address to mint tokens to (from the project which receives fees), and pass along to the
505
- /// ruleset's data hook and pay hook if applicable.
504
+ /// @param beneficiary The address to mint fee-project tokens to (and pass along to the fee project's
505
+ /// data/pay hooks). If `address(0)`, the fee is routed via `addToBalanceOf` instead of `pay`, crediting the
506
+ /// fee project's balance directly without minting fee-project tokens. This honors the protocol-fee intent
507
+ /// (the fee project still receives the value) when no beneficiary is specified, instead of letting `pay`
508
+ /// revert inside `mintTokensOf` and having the catch path forgive the fee.
506
509
  /// @param feeTerminal The terminal that'll receive the fees.
507
510
  function executeProcessFee(
508
511
  uint256 projectId,
@@ -523,14 +526,24 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
523
526
  // Send the projectId in the metadata.
524
527
  bytes memory metadata = bytes(abi.encodePacked(projectId));
525
528
 
526
- _efficientPay({
527
- terminal: feeTerminal,
528
- projectId: JBConstants.FEE_BENEFICIARY_PROJECT_ID,
529
- token: token,
530
- amount: amount,
531
- beneficiary: beneficiary,
532
- metadata: metadata
533
- });
529
+ if (beneficiary == address(0)) {
530
+ _efficientAddToBalance({
531
+ terminal: feeTerminal,
532
+ projectId: JBConstants.FEE_BENEFICIARY_PROJECT_ID,
533
+ token: token,
534
+ amount: amount,
535
+ metadata: metadata
536
+ });
537
+ } else {
538
+ _efficientPay({
539
+ terminal: feeTerminal,
540
+ projectId: JBConstants.FEE_BENEFICIARY_PROJECT_ID,
541
+ token: token,
542
+ amount: amount,
543
+ beneficiary: beneficiary,
544
+ metadata: metadata
545
+ });
546
+ }
534
547
  }
535
548
 
536
549
  /// @notice Transfer funds to an address.
@@ -613,7 +626,11 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
613
626
  }
614
627
 
615
628
  // Transfer the balance minus the fee to the new terminal.
616
- uint256 migrationAmount = balance - feeAmount;
629
+ uint256 migrationAmount;
630
+ // `_takeFeeFrom` calculated `feeAmount` from `balance`, so it cannot exceed `balance`.
631
+ unchecked {
632
+ migrationAmount = balance - feeAmount;
633
+ }
617
634
 
618
635
  _externalAddToBalance({
619
636
  terminal: to, projectId: projectId, token: token, amount: migrationAmount, metadata: bytes("")
@@ -677,7 +694,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
677
694
 
678
695
  // Set the beneficiary token count.
679
696
  if (beneficiaryBalanceAfter > beneficiaryBalanceBefore) {
680
- beneficiaryTokenCount = beneficiaryBalanceAfter - beneficiaryBalanceBefore;
697
+ // Guarded by the comparison above.
698
+ unchecked {
699
+ beneficiaryTokenCount = beneficiaryBalanceAfter - beneficiaryBalanceBefore;
700
+ }
681
701
  }
682
702
 
683
703
  // The token count for the beneficiary must be greater than or equal to the specified minimum.
@@ -724,7 +744,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
724
744
  // reverting.
725
745
  // A `FeeReverted` event is emitted so the forgiveness is observable off-chain.
726
746
  delete _heldFeesOf[projectId][token][currentIndex];
727
- _nextHeldFeeIndexOf[projectId][token] = currentIndex + 1;
747
+ // `currentIndex` was proven to be within the held-fee array.
748
+ unchecked {
749
+ _nextHeldFeeIndexOf[projectId][token] = currentIndex + 1;
750
+ }
728
751
 
729
752
  // Restore the originating fee-paying call's referral project for the duration of this fee's processing
730
753
  // so the credit in `_processFee` attributes to the right (chain, project) pair. No save needed:
@@ -1866,6 +1889,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1866
1889
  });
1867
1890
 
1868
1891
  _recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
1892
+ // The store balance was credited first; this mirrors that bounded increase for fee recovery.
1893
+ unchecked {
1894
+ _feeFreeSurplusOf[projectId][token] += amount;
1895
+ }
1869
1896
  }
1870
1897
  }
1871
1898
 
@@ -1874,7 +1901,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1874
1901
  /// @param token The token to record the added balance for.
1875
1902
  /// @param amount The amount of the token to record, as a fixed point number with the same number of decimals as
1876
1903
  /// this terminal.
1877
- function _recordAddedBalanceFor(uint256 projectId, address token, uint256 amount) internal {
1904
+ function _recordAddedBalanceFor(uint256 projectId, address token, uint256 amount) private {
1878
1905
  STORE.recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
1879
1906
  }
1880
1907
 
@@ -2158,7 +2185,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
2158
2185
  projectId: projectId,
2159
2186
  token: token,
2160
2187
  amount: amountPaidOut,
2161
- // The project owner will receive tokens minted by paying the platform fee.
2188
+ // The `feeBeneficiary` will receive the fee-project tokens minted in exchange for the
2189
+ // platform fee paid in terminal tokens.
2162
2190
  beneficiary: feeBeneficiary,
2163
2191
  shouldHoldFees: ruleset.holdFees()
2164
2192
  }));
@@ -43,6 +43,7 @@ contract JBTerminalStore is IJBTerminalStore {
43
43
  error JBTerminalStore_AccountingContextDecimalsMismatch(
44
44
  address token, uint256 providedDecimals, uint256 expectedDecimals
45
45
  );
46
+ error JBTerminalStore_AccountingContextDecimalsOutOfRange(address token, uint256 decimals);
46
47
  error JBTerminalStore_AddingAccountingContextNotAllowed(uint256 projectId, uint256 rulesetId, address terminal);
47
48
  error JBTerminalStore_InadequateControllerAllowance(uint256 amount, uint256 allowance);
48
49
 
@@ -212,6 +213,12 @@ contract JBTerminalStore is IJBTerminalStore {
212
213
  revert JBTerminalStore_AccountingContextAlreadySet({token: context.token});
213
214
  }
214
215
 
216
+ if (context.decimals > 36) {
217
+ revert JBTerminalStore_AccountingContextDecimalsOutOfRange({
218
+ token: context.token, decimals: context.decimals
219
+ });
220
+ }
221
+
215
222
  // Keep track of a flag indicating if we know the provided decimals are incorrect.
216
223
  bool knownInvalidDecimals;
217
224
  uint256 expectedDecimals;
@@ -222,16 +229,22 @@ contract JBTerminalStore is IJBTerminalStore {
222
229
  expectedDecimals = 18;
223
230
  } else if (context.token != JBConstants.NATIVE_TOKEN && context.token.code.length > 0) {
224
231
  try IERC20Metadata(context.token).decimals() returns (uint8 decimals) {
225
- if (context.decimals != decimals) {
226
- knownInvalidDecimals = true;
227
- expectedDecimals = decimals;
228
- }
232
+ expectedDecimals = decimals;
229
233
  } catch {
230
234
  // The token didn't support `decimals`.
231
235
  // @dev Non-standard ERC20s that revert on `decimals()` will bypass decimal validation.
232
236
  // The caller is responsible for providing the correct decimals for such tokens.
233
237
  knownInvalidDecimals = false;
238
+ expectedDecimals = context.decimals;
234
239
  }
240
+
241
+ if (expectedDecimals > 36) {
242
+ revert JBTerminalStore_AccountingContextDecimalsOutOfRange({
243
+ token: context.token, decimals: expectedDecimals
244
+ });
245
+ }
246
+
247
+ if (context.decimals != expectedDecimals) knownInvalidDecimals = true;
235
248
  }
236
249
 
237
250
  // Make sure the decimals are correct.
@@ -1442,7 +1455,8 @@ contract JBTerminalStore is IJBTerminalStore {
1442
1455
  referralChainId: referralChainId,
1443
1456
  referralProjectId: bareProjectId,
1444
1457
  amount: amount,
1445
- newTotal: newTotal
1458
+ newTotal: newTotal,
1459
+ caller: msg.sender
1446
1460
  });
1447
1461
  }
1448
1462
 
@@ -102,7 +102,8 @@ interface IJBController is IERC165, IJBProjectUriRegistry, IJBDirectoryAccessCon
102
102
  /// @param projectId The ID of the project.
103
103
  /// @param hook The split hook that reverted.
104
104
  /// @param reason The revert reason.
105
- event SplitHookReverted(uint256 indexed projectId, address hook, bytes reason);
105
+ /// @param caller The address that called the distribution function.
106
+ event SplitHookReverted(uint256 indexed projectId, address hook, bytes reason, address caller);
106
107
 
107
108
  /// @notice Reserved tokens were sent to a specific split.
108
109
  /// @param projectId The ID of the project.
@@ -25,12 +25,14 @@ interface IJBTerminalStore {
25
25
  /// @param referralProjectId The referrer's bare project ID on `referralChainId` (no chain bits).
26
26
  /// @param amount The fee amount credited, in the terminal's accounting-context units.
27
27
  /// @param newTotal The new value of `totalFeeVolumeOf[terminal]` after this credit.
28
+ /// @param caller The address that recorded the credit.
28
29
  event ReferralCredit(
29
30
  address indexed terminal,
30
31
  uint256 indexed referralChainId,
31
32
  uint256 indexed referralProjectId,
32
33
  uint256 amount,
33
- uint256 newTotal
34
+ uint256 newTotal,
35
+ address caller
34
36
  );
35
37
 
36
38
  /// @notice The directory of terminals and controllers for projects.
@@ -92,7 +92,10 @@ library JBRulesetMetadataResolver {
92
92
 
93
93
  /// @notice Pack the funding cycle metadata.
94
94
  /// @param rulesetMetadata The ruleset metadata to validate and pack.
95
- /// @return packed The packed uint256 of all metadata params. The first 8 bits specify the version.
95
+ /// @return packed The packed uint256 of all metadata params. The first 4 bits (bits 0-3) specify the version;
96
+ /// supported values are 0-15. A future protocol version that needs more than 16 distinct metadata layouts must
97
+ /// widen this field before assigning a new version number, otherwise the high bits will silently spill into
98
+ /// the `reservedPercent` field that begins at bit 4.
96
99
  function packRulesetMetadata(JBRulesetMetadata memory rulesetMetadata) internal pure returns (uint256 packed) {
97
100
  // version 1 in the bits 0-3 (4 bits).
98
101
  packed = 1;