@bananapus/buyback-hook-v6 0.0.7 → 0.0.8

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/buyback-hook-v6",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,8 +18,8 @@
18
18
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-buyback-hook-v6'"
19
19
  },
20
20
  "dependencies": {
21
- "@bananapus/core-v6": "^0.0.9",
22
- "@bananapus/permission-ids-v6": "^0.0.4",
21
+ "@bananapus/core-v6": "^0.0.10",
22
+ "@bananapus/permission-ids-v6": "^0.0.5",
23
23
  "@openzeppelin/contracts": "^5.2.0",
24
24
  "@uniswap/v4-core": "^1.0.2"
25
25
  },
@@ -29,11 +29,11 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
29
29
  import {Context} from "@openzeppelin/contracts/utils/Context.sol";
30
30
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
31
31
  import {mulDiv} from "@prb/math/src/Common.sol";
32
- import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
33
32
  import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
34
33
  import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
35
34
  import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
36
35
  import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
36
+ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
37
37
  import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
38
38
  import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
39
39
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
@@ -181,146 +181,6 @@ contract JBBuybackHook is JBPermissioned, ERC2771Context, IUnlockCallback, IJBBu
181
181
  WETH = weth;
182
182
  }
183
183
 
184
- //*********************************************************************//
185
- // ------------------------- external views -------------------------- //
186
- //*********************************************************************//
187
-
188
- /// @notice To fulfill the `IJBRulesetDataHook` interface.
189
- /// @dev Pass cash out context back to the terminal without changes.
190
- function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
191
- external
192
- pure
193
- override
194
- returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory hookSpecifications)
195
- {
196
- return (context.cashOutTaxRate, context.cashOutCount, context.totalSupply, hookSpecifications);
197
- }
198
-
199
- /// @notice The `IJBRulesetDataHook` implementation which determines whether tokens should be minted from the
200
- /// project or bought from the pool.
201
- /// @param context Payment context passed to the data hook by `terminalStore.recordPaymentFrom(...)`.
202
- /// `context.metadata` can specify a Uniswap quote and specify how much of the payment should be used to swap.
203
- /// If `context.metadata` does not specify a quote, one will be calculated based on the TWAP.
204
- /// If `context.metadata` does not specify how much of the payment should be used, the hook uses the full amount
205
- /// paid in.
206
- /// @return weight The weight to use for minting. 0 if all tokens come from the swap.
207
- /// @return hookSpecifications Specifications containing pay hooks, as well as the amount and metadata to send to
208
- /// them. Empty if only minting.
209
- function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
210
- external
211
- view
212
- override
213
- returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
214
- {
215
- // Keep a reference to the amount paid in.
216
- uint256 totalPaid = context.amount.value;
217
-
218
- // Keep a reference to the weight.
219
- weight = context.weight;
220
-
221
- // Keep a reference to the minimum number of tokens expected from the swap.
222
- uint256 minimumSwapAmountOut;
223
-
224
- // Keep a reference to the amount to be used to swap (out of `totalPaid`).
225
- uint256 amountToSwapWith;
226
-
227
- // Scoped section to prevent stack too deep.
228
- {
229
- // Unpack the quote specified by the payer/client (typically from the pool).
230
- bytes4 metadataId = JBMetadataResolver.getId("quote");
231
- (bool quoteExists, bytes memory metadata) = JBMetadataResolver.getDataFor(metadataId, context.metadata);
232
- if (quoteExists) (amountToSwapWith, minimumSwapAmountOut) = abi.decode(metadata, (uint256, uint256));
233
- }
234
-
235
- // If the amount to swap with is greater than the actual amount paid in, revert.
236
- if (amountToSwapWith > totalPaid) revert JBBuybackHook_InsufficientPayAmount(amountToSwapWith, totalPaid);
237
-
238
- // If the payer/client did not specify an amount to use towards the swap, use the `totalPaid`.
239
- if (amountToSwapWith == 0) amountToSwapWith = totalPaid;
240
-
241
- // Get a reference to the controller.
242
- IJBController controller = IJBController(address(DIRECTORY.controllerOf(context.projectId)));
243
-
244
- // Get a reference to the ruleset.
245
- // slither-disable-next-line unused-return
246
- (JBRuleset memory ruleset,) = controller.currentRulesetOf(context.projectId);
247
-
248
- // If the hook should base its weight on a currency other than the terminal's currency, determine the factor.
249
- uint256 weightRatio = context.amount.currency == ruleset.baseCurrency()
250
- ? 10 ** context.amount.decimals
251
- : PRICES.pricePerUnitOf({
252
- projectId: context.projectId,
253
- pricingCurrency: context.amount.currency,
254
- unitCurrency: ruleset.baseCurrency(),
255
- decimals: context.amount.decimals
256
- });
257
-
258
- // Calculate how many tokens would be minted by a direct payment to the project.
259
- uint256 tokenCountWithoutHook = mulDiv(amountToSwapWith, weight, weightRatio);
260
-
261
- // Keep a reference to the project's token.
262
- address projectToken = projectTokenOf[context.projectId];
263
-
264
- // Keep a reference to the token being used by the terminal. Default to wETH if the terminal uses native.
265
- address terminalToken = context.amount.token == JBConstants.NATIVE_TOKEN ? address(WETH) : context.amount.token;
266
-
267
- // Always compute the TWAP-based minimum.
268
- uint256 twapMinimum = _getQuote({
269
- projectId: context.projectId,
270
- projectToken: projectToken,
271
- amountIn: amountToSwapWith,
272
- terminalToken: terminalToken
273
- });
274
-
275
- // Use the higher of the payer's quote and the TWAP quote.
276
- // This prevents a stale/malicious payer quote from getting a worse deal than the oracle suggests.
277
- if (twapMinimum > minimumSwapAmountOut) minimumSwapAmountOut = twapMinimum;
278
-
279
- // If the minimum amount from the swap exceeds what minting directly would yield, swap.
280
- if (tokenCountWithoutHook < minimumSwapAmountOut) {
281
- bool projectTokenIs0 = address(projectToken) < terminalToken;
282
-
283
- // Specify this hook as the one to use.
284
- hookSpecifications = new JBPayHookSpecification[](1);
285
- hookSpecifications[0] = JBPayHookSpecification({
286
- hook: IJBPayHook(this),
287
- amount: amountToSwapWith,
288
- metadata: abi.encode(
289
- projectTokenIs0,
290
- totalPaid == amountToSwapWith ? 0 : totalPaid - amountToSwapWith,
291
- minimumSwapAmountOut,
292
- controller
293
- )
294
- });
295
-
296
- // All the minting will be done in `afterPayRecordedWith`. Return a weight of 0.
297
- return (0, hookSpecifications);
298
- }
299
- }
300
-
301
- /// @notice Required by the `IJBRulesetDataHook` interfaces. Return false to not leak any permissions.
302
- function hasMintPermissionFor(uint256, JBRuleset memory, address) external pure override returns (bool) {
303
- return false;
304
- }
305
-
306
- //*********************************************************************//
307
- // -------------------------- public views --------------------------- //
308
- //*********************************************************************//
309
-
310
- /// @notice Returns the PoolKey for a given project and terminal token pair.
311
- /// @param projectId The ID of the project.
312
- /// @param terminalToken The terminal token address (normalized to WETH for native).
313
- /// @return key The V4 PoolKey.
314
- function poolKeyOf(uint256 projectId, address terminalToken) public view override returns (PoolKey memory key) {
315
- return _poolKeyOf[projectId][terminalToken];
316
- }
317
-
318
- function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
319
- return interfaceId == type(IJBRulesetDataHook).interfaceId || interfaceId == type(IJBPayHook).interfaceId
320
- || interfaceId == type(IJBBuybackHook).interfaceId || interfaceId == type(IJBPermissioned).interfaceId
321
- || interfaceId == type(IERC165).interfaceId;
322
- }
323
-
324
184
  //*********************************************************************//
325
185
  // ---------------------- external transactions ---------------------- //
326
186
  //*********************************************************************//
@@ -620,94 +480,147 @@ contract JBBuybackHook is JBPermissioned, ERC2771Context, IUnlockCallback, IJBBu
620
480
  receive() external payable {}
621
481
 
622
482
  //*********************************************************************//
623
- // -------------------------- internal views ------------------------- //
483
+ // ------------------------- external views -------------------------- //
624
484
  //*********************************************************************//
625
485
 
626
- /// @dev `ERC-2771` specifies the context as being a single address (20 bytes).
627
- function _contextSuffixLength() internal view override(ERC2771Context, Context) returns (uint256) {
628
- return super._contextSuffixLength();
486
+ /// @notice To fulfill the `IJBRulesetDataHook` interface.
487
+ /// @dev Pass cash out context back to the terminal without changes.
488
+ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
489
+ external
490
+ pure
491
+ override
492
+ returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory hookSpecifications)
493
+ {
494
+ return (context.cashOutTaxRate, context.cashOutCount, context.totalSupply, hookSpecifications);
629
495
  }
630
496
 
631
- /// @notice Get a quote based on the oracle hook TWAP or spot price.
632
- /// @param projectId The ID of the project.
633
- /// @param projectToken The project token being swapped for.
634
- /// @param amountIn The number of terminal tokens being used to swap.
635
- /// @param terminalToken The terminal token being paid in (normalized to WETH for native).
636
- /// @return amountOut The minimum number of tokens to receive based on the TWAP and slippage.
637
- function _getQuote(
638
- uint256 projectId,
639
- address projectToken,
640
- uint256 amountIn,
641
- address terminalToken
642
- )
643
- internal
497
+ /// @notice The `IJBRulesetDataHook` implementation which determines whether tokens should be minted from the
498
+ /// project or bought from the pool.
499
+ /// @param context Payment context passed to the data hook by `terminalStore.recordPaymentFrom(...)`.
500
+ /// `context.metadata` can specify a Uniswap quote and specify how much of the payment should be used to swap.
501
+ /// If `context.metadata` does not specify a quote, one will be calculated based on the TWAP.
502
+ /// If `context.metadata` does not specify how much of the payment should be used, the hook uses the full amount
503
+ /// paid in.
504
+ /// @return weight The weight to use for minting. 0 if all tokens come from the swap.
505
+ /// @return hookSpecifications Specifications containing pay hooks, as well as the amount and metadata to send to
506
+ /// them. Empty if only minting.
507
+ function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
508
+ external
644
509
  view
645
- returns (uint256 amountOut)
510
+ override
511
+ returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
646
512
  {
647
- // Get the pool key for this project/terminal token pair.
648
- PoolKey memory key = _poolKeyOf[projectId][terminalToken];
513
+ // Keep a reference to the amount paid in.
514
+ uint256 totalPaid = context.amount.value;
649
515
 
650
- // Make sure a pool has been configured.
651
- if (!_poolIsSet[projectId][terminalToken]) return 0;
516
+ // Keep a reference to the weight.
517
+ weight = context.weight;
652
518
 
653
- // Get the TWAP window.
654
- uint256 twapWindow = twapWindowOf[projectId];
519
+ // Keep a reference to the minimum number of tokens expected from the swap.
520
+ uint256 minimumSwapAmountOut;
655
521
 
656
- // Query the oracle hook (or spot if twapWindow is 0).
657
- int24 arithmeticMeanTick;
658
- uint128 meanLiquidity;
659
- (amountOut, arithmeticMeanTick, meanLiquidity) = JBSwapLib.getQuoteFromOracle({
660
- poolManager: POOL_MANAGER,
661
- key: key,
662
- twapWindow: uint32(twapWindow),
663
- amountIn: uint128(amountIn),
664
- baseToken: terminalToken,
665
- quoteToken: projectToken
666
- });
522
+ // Keep a reference to the amount to be used to swap (out of `totalPaid`).
523
+ uint256 amountToSwapWith;
667
524
 
668
- // If oracle returned 0, no quote available — trigger mint fallback.
669
- if (amountOut == 0) return 0;
525
+ // Scoped section to prevent stack too deep.
526
+ {
527
+ // Unpack the quote specified by the payer/client (typically from the pool).
528
+ bytes4 metadataId = JBMetadataResolver.getId("quote");
529
+ (bool quoteExists, bytes memory metadata) = JBMetadataResolver.getDataFor(metadataId, context.metadata);
530
+ if (quoteExists) (amountToSwapWith, minimumSwapAmountOut) = abi.decode(metadata, (uint256, uint256));
531
+ }
670
532
 
671
- // If there's no liquidity data, return 0 to trigger mint.
672
- if (meanLiquidity == 0) return 0;
533
+ // If the amount to swap with is greater than the actual amount paid in, revert.
534
+ if (amountToSwapWith > totalPaid) revert JBBuybackHook_InsufficientPayAmount(amountToSwapWith, totalPaid);
673
535
 
674
- // Calculate price impact.
675
- bool zeroForOne = terminalToken < projectToken;
676
- uint160 sqrtP = TickMath.getSqrtPriceAtTick(arithmeticMeanTick);
677
- uint256 impact = JBSwapLib.calculateImpact(amountIn, meanLiquidity, sqrtP, zeroForOne);
536
+ // If the payer/client did not specify an amount to use towards the swap, use the `totalPaid`.
537
+ if (amountToSwapWith == 0) amountToSwapWith = totalPaid;
678
538
 
679
- // Get the pool fee in bps (V4 fees are in hundredths of a bip, so divide by 100).
680
- uint256 poolFeeBps = uint256(key.fee) / 100;
539
+ // Get a reference to the controller.
540
+ IJBController controller = IJBController(address(DIRECTORY.controllerOf(context.projectId)));
681
541
 
682
- // Calculate continuous sigmoid slippage tolerance.
683
- uint256 slippageTolerance = JBSwapLib.getSlippageTolerance(impact, poolFeeBps);
542
+ // Get a reference to the ruleset.
543
+ // slither-disable-next-line unused-return
544
+ (JBRuleset memory ruleset,) = controller.currentRulesetOf(context.projectId);
684
545
 
685
- // If the slippage tolerance is the maximum, return 0 to trigger mint.
686
- if (slippageTolerance >= TWAP_SLIPPAGE_DENOMINATOR) return 0;
546
+ // If the hook should base its weight on a currency other than the terminal's currency, determine the factor.
547
+ uint256 weightRatio = context.amount.currency == ruleset.baseCurrency()
548
+ ? 10 ** context.amount.decimals
549
+ : PRICES.pricePerUnitOf({
550
+ projectId: context.projectId,
551
+ pricingCurrency: context.amount.currency,
552
+ unitCurrency: ruleset.baseCurrency(),
553
+ decimals: context.amount.decimals
554
+ });
687
555
 
688
- // Apply slippage to the oracle quote.
689
- amountOut -= (amountOut * slippageTolerance) / TWAP_SLIPPAGE_DENOMINATOR;
556
+ // Calculate how many tokens would be minted by a direct payment to the project.
557
+ uint256 tokenCountWithoutHook = mulDiv(amountToSwapWith, weight, weightRatio);
558
+
559
+ // Keep a reference to the project's token.
560
+ address projectToken = projectTokenOf[context.projectId];
561
+
562
+ // Keep a reference to the token being used by the terminal. Default to wETH if the terminal uses native.
563
+ address terminalToken = context.amount.token == JBConstants.NATIVE_TOKEN ? address(WETH) : context.amount.token;
564
+
565
+ // Always compute the TWAP-based minimum.
566
+ uint256 twapMinimum = _getQuote({
567
+ projectId: context.projectId,
568
+ projectToken: projectToken,
569
+ amountIn: amountToSwapWith,
570
+ terminalToken: terminalToken
571
+ });
572
+
573
+ // Use the higher of the payer's quote and the TWAP quote.
574
+ // This prevents a stale/malicious payer quote from getting a worse deal than the oracle suggests.
575
+ if (twapMinimum > minimumSwapAmountOut) minimumSwapAmountOut = twapMinimum;
576
+
577
+ // If the minimum amount from the swap exceeds what minting directly would yield, swap.
578
+ if (tokenCountWithoutHook < minimumSwapAmountOut) {
579
+ bool projectTokenIs0 = address(projectToken) < terminalToken;
580
+
581
+ // Specify this hook as the one to use.
582
+ hookSpecifications = new JBPayHookSpecification[](1);
583
+ hookSpecifications[0] = JBPayHookSpecification({
584
+ hook: IJBPayHook(this),
585
+ amount: amountToSwapWith,
586
+ metadata: abi.encode(
587
+ projectTokenIs0,
588
+ totalPaid == amountToSwapWith ? 0 : totalPaid - amountToSwapWith,
589
+ minimumSwapAmountOut,
590
+ controller
591
+ )
592
+ });
593
+
594
+ // All the minting will be done in `afterPayRecordedWith`. Return a weight of 0.
595
+ return (0, hookSpecifications);
596
+ }
690
597
  }
691
598
 
692
- /// @notice The calldata. Preferred to use over `msg.data`.
693
- function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
694
- return ERC2771Context._msgData();
599
+ /// @notice Required by the `IJBRulesetDataHook` interfaces. Return false to not leak any permissions.
600
+ function hasMintPermissionFor(uint256, JBRuleset memory, address) external pure override returns (bool) {
601
+ return false;
695
602
  }
696
603
 
697
- /// @notice The message's sender. Preferred to use over `msg.sender`.
698
- function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
699
- return ERC2771Context._msgSender();
604
+ //*********************************************************************//
605
+ // -------------------------- public views --------------------------- //
606
+ //*********************************************************************//
607
+
608
+ /// @notice Returns the PoolKey for a given project and terminal token pair.
609
+ /// @param projectId The ID of the project.
610
+ /// @param terminalToken The terminal token address (normalized to WETH for native).
611
+ /// @return key The V4 PoolKey.
612
+ function poolKeyOf(uint256 projectId, address terminalToken) public view override returns (PoolKey memory key) {
613
+ return _poolKeyOf[projectId][terminalToken];
700
614
  }
701
615
 
702
- /// @notice Returns this contract's balance of the given terminal token.
703
- /// @param token The terminal token address (NATIVE_TOKEN for ETH).
704
- /// @return balance The current balance held by this contract.
705
- function _terminalTokenBalance(address token) internal view returns (uint256 balance) {
706
- return token == JBConstants.NATIVE_TOKEN ? address(this).balance : IERC20(token).balanceOf(address(this));
616
+ function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
617
+ return interfaceId == type(IJBRulesetDataHook).interfaceId || interfaceId == type(IJBPayHook).interfaceId
618
+ || interfaceId == type(IJBBuybackHook).interfaceId || interfaceId == type(IJBPermissioned).interfaceId
619
+ || interfaceId == type(IERC165).interfaceId;
707
620
  }
708
621
 
709
622
  //*********************************************************************//
710
- // ---------------------- internal functions ------------------------- //
623
+ // ---------------------- internal transactions ---------------------- //
711
624
  //*********************************************************************//
712
625
 
713
626
  /// @notice Swap the terminal token to receive project tokens via V4.
@@ -779,4 +692,91 @@ contract JBBuybackHook is JBPermissioned, ERC2771Context, IUnlockCallback, IJBBu
779
692
  });
780
693
  }
781
694
  }
695
+
696
+ //*********************************************************************//
697
+ // -------------------------- internal views ------------------------- //
698
+ //*********************************************************************//
699
+
700
+ /// @dev `ERC-2771` specifies the context as being a single address (20 bytes).
701
+ function _contextSuffixLength() internal view override(ERC2771Context, Context) returns (uint256) {
702
+ return super._contextSuffixLength();
703
+ }
704
+
705
+ /// @notice Get a quote based on the oracle hook TWAP or spot price.
706
+ /// @param projectId The ID of the project.
707
+ /// @param projectToken The project token being swapped for.
708
+ /// @param amountIn The number of terminal tokens being used to swap.
709
+ /// @param terminalToken The terminal token being paid in (normalized to WETH for native).
710
+ /// @return amountOut The minimum number of tokens to receive based on the TWAP and slippage.
711
+ function _getQuote(
712
+ uint256 projectId,
713
+ address projectToken,
714
+ uint256 amountIn,
715
+ address terminalToken
716
+ )
717
+ internal
718
+ view
719
+ returns (uint256 amountOut)
720
+ {
721
+ // Get the pool key for this project/terminal token pair.
722
+ PoolKey memory key = _poolKeyOf[projectId][terminalToken];
723
+
724
+ // Make sure a pool has been configured.
725
+ if (!_poolIsSet[projectId][terminalToken]) return 0;
726
+
727
+ // Get the TWAP window.
728
+ uint256 twapWindow = twapWindowOf[projectId];
729
+
730
+ // Query the oracle hook (or spot if twapWindow is 0).
731
+ int24 arithmeticMeanTick;
732
+ uint128 meanLiquidity;
733
+ (amountOut, arithmeticMeanTick, meanLiquidity) = JBSwapLib.getQuoteFromOracle({
734
+ poolManager: POOL_MANAGER,
735
+ key: key,
736
+ twapWindow: uint32(twapWindow),
737
+ amountIn: uint128(amountIn),
738
+ baseToken: terminalToken,
739
+ quoteToken: projectToken
740
+ });
741
+
742
+ // If oracle returned 0, no quote available — trigger mint fallback.
743
+ if (amountOut == 0) return 0;
744
+
745
+ // If there's no liquidity data, return 0 to trigger mint.
746
+ if (meanLiquidity == 0) return 0;
747
+
748
+ // Calculate price impact.
749
+ bool zeroForOne = terminalToken < projectToken;
750
+ uint160 sqrtP = TickMath.getSqrtPriceAtTick(arithmeticMeanTick);
751
+ uint256 impact = JBSwapLib.calculateImpact(amountIn, meanLiquidity, sqrtP, zeroForOne);
752
+
753
+ // Get the pool fee in bps (V4 fees are in hundredths of a bip, so divide by 100).
754
+ uint256 poolFeeBps = uint256(key.fee) / 100;
755
+
756
+ // Calculate continuous sigmoid slippage tolerance.
757
+ uint256 slippageTolerance = JBSwapLib.getSlippageTolerance(impact, poolFeeBps);
758
+
759
+ // If the slippage tolerance is the maximum, return 0 to trigger mint.
760
+ if (slippageTolerance >= TWAP_SLIPPAGE_DENOMINATOR) return 0;
761
+
762
+ // Apply slippage to the oracle quote.
763
+ amountOut -= (amountOut * slippageTolerance) / TWAP_SLIPPAGE_DENOMINATOR;
764
+ }
765
+
766
+ /// @notice The calldata. Preferred to use over `msg.data`.
767
+ function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
768
+ return ERC2771Context._msgData();
769
+ }
770
+
771
+ /// @notice The message's sender. Preferred to use over `msg.sender`.
772
+ function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
773
+ return ERC2771Context._msgSender();
774
+ }
775
+
776
+ /// @notice Returns this contract's balance of the given terminal token.
777
+ /// @param token The terminal token address (NATIVE_TOKEN for ETH).
778
+ /// @return balance The current balance held by this contract.
779
+ function _terminalTokenBalance(address token) internal view returns (uint256 balance) {
780
+ return token == JBConstants.NATIVE_TOKEN ? address(this).balance : IERC20(token).balanceOf(address(this));
781
+ }
782
782
  }
@@ -80,77 +80,6 @@ contract JBBuybackHookRegistry is IJBBuybackHookRegistry, ERC2771Context, JBPerm
80
80
  PROJECTS = projects;
81
81
  }
82
82
 
83
- //*********************************************************************//
84
- // ------------------------- external views -------------------------- //
85
- //*********************************************************************//
86
-
87
- /// @notice To fulfill the `IJBRulesetDataHook` interface.
88
- /// @dev Pass cash out context back to the terminal without changes.
89
- /// @param context The cash out context passed in by the terminal.
90
- function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
91
- external
92
- pure
93
- override
94
- returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory hookSpecifications)
95
- {
96
- return (context.cashOutTaxRate, context.cashOutCount, context.totalSupply, hookSpecifications);
97
- }
98
-
99
- /// @notice Forward the call to the hook for the project.
100
- function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
101
- external
102
- view
103
- override
104
- returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
105
- {
106
- // Get the hook for the project (falls back to default).
107
- IJBRulesetDataHook hook = _hookOf[context.projectId];
108
- if (hook == IJBRulesetDataHook(address(0))) hook = defaultHook;
109
-
110
- // Forward the call to the hook.
111
- // slither-disable-next-line unused-return
112
- return hook.beforePayRecordedWith(context);
113
- }
114
-
115
- /// @notice Make sure the hook has mint permission.
116
- /// @param projectId The ID of the project to check the mint permission for.
117
- /// @param addr The address to check the mint permission for.
118
- /// @return Whether the address has mint permission.
119
- function hasMintPermissionFor(
120
- uint256 projectId,
121
- JBRuleset memory,
122
- address addr
123
- )
124
- external
125
- view
126
- override
127
- returns (bool)
128
- {
129
- // Get the hook for the project (falls back to default).
130
- IJBRulesetDataHook hook = _hookOf[projectId];
131
- if (hook == IJBRulesetDataHook(address(0))) hook = defaultHook;
132
-
133
- // Make sure the hook has mint permission.
134
- return addr == address(hook);
135
- }
136
-
137
- /// @notice The hook for the given project, or the default hook if none is set.
138
- /// @param projectId The ID of the project to get the hook for.
139
- /// @return hook The hook for the project.
140
- function hookOf(uint256 projectId) external view override returns (IJBRulesetDataHook hook) {
141
- hook = _hookOf[projectId];
142
- if (hook == IJBRulesetDataHook(address(0))) hook = defaultHook;
143
- }
144
-
145
- //*********************************************************************//
146
- // -------------------------- public views --------------------------- //
147
- //*********************************************************************//
148
-
149
- function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
150
- return interfaceId == type(IJBBuybackHookRegistry).interfaceId
151
- || interfaceId == type(IJBRulesetDataHook).interfaceId || interfaceId == type(IERC165).interfaceId;
152
- }
153
-
154
83
  //*********************************************************************//
155
84
  // ---------------------- external transactions ---------------------- //
156
85
  //*********************************************************************//
@@ -248,6 +177,77 @@ contract JBBuybackHookRegistry is IJBBuybackHookRegistry, ERC2771Context, JBPerm
248
177
  emit JBBuybackHookRegistry_SetHook(projectId, hook);
249
178
  }
250
179
 
180
+ //*********************************************************************//
181
+ // ------------------------- external views -------------------------- //
182
+ //*********************************************************************//
183
+
184
+ /// @notice To fulfill the `IJBRulesetDataHook` interface.
185
+ /// @dev Pass cash out context back to the terminal without changes.
186
+ /// @param context The cash out context passed in by the terminal.
187
+ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
188
+ external
189
+ pure
190
+ override
191
+ returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory hookSpecifications)
192
+ {
193
+ return (context.cashOutTaxRate, context.cashOutCount, context.totalSupply, hookSpecifications);
194
+ }
195
+
196
+ /// @notice Forward the call to the hook for the project.
197
+ function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
198
+ external
199
+ view
200
+ override
201
+ returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
202
+ {
203
+ // Get the hook for the project (falls back to default).
204
+ IJBRulesetDataHook hook = _hookOf[context.projectId];
205
+ if (hook == IJBRulesetDataHook(address(0))) hook = defaultHook;
206
+
207
+ // Forward the call to the hook.
208
+ // slither-disable-next-line unused-return
209
+ return hook.beforePayRecordedWith(context);
210
+ }
211
+
212
+ /// @notice Make sure the hook has mint permission.
213
+ /// @param projectId The ID of the project to check the mint permission for.
214
+ /// @param addr The address to check the mint permission for.
215
+ /// @return Whether the address has mint permission.
216
+ function hasMintPermissionFor(
217
+ uint256 projectId,
218
+ JBRuleset memory,
219
+ address addr
220
+ )
221
+ external
222
+ view
223
+ override
224
+ returns (bool)
225
+ {
226
+ // Get the hook for the project (falls back to default).
227
+ IJBRulesetDataHook hook = _hookOf[projectId];
228
+ if (hook == IJBRulesetDataHook(address(0))) hook = defaultHook;
229
+
230
+ // Make sure the hook has mint permission.
231
+ return addr == address(hook);
232
+ }
233
+
234
+ /// @notice The hook for the given project, or the default hook if none is set.
235
+ /// @param projectId The ID of the project to get the hook for.
236
+ /// @return hook The hook for the project.
237
+ function hookOf(uint256 projectId) external view override returns (IJBRulesetDataHook hook) {
238
+ hook = _hookOf[projectId];
239
+ if (hook == IJBRulesetDataHook(address(0))) hook = defaultHook;
240
+ }
241
+
242
+ //*********************************************************************//
243
+ // -------------------------- public views --------------------------- //
244
+ //*********************************************************************//
245
+
246
+ function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
247
+ return interfaceId == type(IJBBuybackHookRegistry).interfaceId
248
+ || interfaceId == type(IJBRulesetDataHook).interfaceId || interfaceId == type(IERC165).interfaceId;
249
+ }
250
+
251
251
  //*********************************************************************//
252
252
  // -------------------------- internal views ------------------------- //
253
253
  //*********************************************************************//
@@ -13,28 +13,27 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
13
13
 
14
14
  import {IWETH9} from "./external/IWETH9.sol";
15
15
 
16
- /// @notice A hook that facilitates buybacks of project tokens using Uniswap V4 pools.
17
16
  interface IJBBuybackHook is IJBPayHook, IJBRulesetDataHook {
18
- /// @notice Emitted when tokens are minted instead of swapped.
19
- /// @param projectId The ID of the project whose tokens were minted.
20
- /// @param leftoverAmount The amount left over after minting.
21
- /// @param tokenCount The number of tokens minted.
22
- /// @param caller The address that called the function.
17
+ /// @notice Emitted when leftover terminal tokens are minted as project tokens.
18
+ /// @param projectId The ID of the project whose tokens are being minted.
19
+ /// @param leftoverAmount The amount of terminal tokens used for minting.
20
+ /// @param tokenCount The number of project tokens minted.
21
+ /// @param caller The address that triggered the mint.
23
22
  event Mint(uint256 indexed projectId, uint256 leftoverAmount, uint256 tokenCount, address caller);
24
23
 
25
- /// @notice Emitted when a pool is added for a project and terminal token.
26
- /// @param projectId The ID of the project the pool was added for.
27
- /// @param terminalToken The terminal token address.
24
+ /// @notice Emitted when a pool is added for a project and terminal token pair.
25
+ /// @param projectId The ID of the project the pool is being added for.
26
+ /// @param terminalToken The address of the terminal token.
28
27
  /// @param poolId The ID of the Uniswap V4 pool.
29
- /// @param caller The address that called the function.
28
+ /// @param caller The address that added the pool.
30
29
  event PoolAdded(uint256 indexed projectId, address indexed terminalToken, PoolId poolId, address caller);
31
30
 
32
- /// @notice Emitted when a swap is performed through the buyback hook.
33
- /// @param projectId The ID of the project the swap was performed for.
34
- /// @param amountToSwapWith The amount used for the swap.
31
+ /// @notice Emitted when terminal tokens are swapped for project tokens via the Uniswap V4 pool.
32
+ /// @param projectId The ID of the project whose tokens are being swapped for.
33
+ /// @param amountToSwapWith The amount of terminal tokens used for the swap.
35
34
  /// @param poolId The ID of the Uniswap V4 pool used.
36
35
  /// @param amountReceived The amount of project tokens received from the swap.
37
- /// @param caller The address that called the function.
36
+ /// @param caller The address that triggered the swap.
38
37
  event Swap(
39
38
  uint256 indexed projectId,
40
39
  uint256 amountToSwapWith,
@@ -43,11 +42,11 @@ interface IJBBuybackHook is IJBPayHook, IJBRulesetDataHook {
43
42
  address caller
44
43
  );
45
44
 
46
- /// @notice Emitted when the TWAP window is changed for a project.
47
- /// @param projectId The ID of the project whose TWAP window was changed.
48
- /// @param oldWindow The previous TWAP window value.
49
- /// @param newWindow The new TWAP window value.
50
- /// @param caller The address that called the function.
45
+ /// @notice Emitted when the TWAP window for a project is changed.
46
+ /// @param projectId The ID of the project whose TWAP window is being changed.
47
+ /// @param oldWindow The previous TWAP window in seconds.
48
+ /// @param newWindow The new TWAP window in seconds.
49
+ /// @param caller The address that changed the TWAP window.
51
50
  event TwapWindowChanged(uint256 indexed projectId, uint256 oldWindow, uint256 newWindow, address caller);
52
51
 
53
52
  /// @notice The directory of terminals and controllers.
@@ -62,7 +61,7 @@ interface IJBBuybackHook is IJBPayHook, IJBRulesetDataHook {
62
61
  /// @return The minimum TWAP window in seconds.
63
62
  function MIN_TWAP_WINDOW() external view returns (uint256);
64
63
 
65
- /// @notice The Uniswap V4 pool manager.
64
+ /// @notice The Uniswap V4 PoolManager singleton.
66
65
  /// @return The pool manager contract.
67
66
  function POOL_MANAGER() external view returns (IPoolManager);
68
67
 
@@ -82,22 +81,22 @@ interface IJBBuybackHook is IJBPayHook, IJBRulesetDataHook {
82
81
  /// @return The slippage denominator.
83
82
  function TWAP_SLIPPAGE_DENOMINATOR() external view returns (uint256);
84
83
 
85
- /// @notice The WETH contract.
86
- /// @return The WETH9 contract.
84
+ /// @notice The wETH contract.
85
+ /// @return The WETH contract.
87
86
  function WETH() external view returns (IWETH9);
88
87
 
89
- /// @notice The Uniswap V4 pool key for a given project and terminal token.
88
+ /// @notice The PoolKey for a given project and terminal token pair.
90
89
  /// @param projectId The ID of the project.
91
90
  /// @param terminalToken The terminal token address.
92
- /// @return key The pool key.
91
+ /// @return key The V4 PoolKey.
93
92
  function poolKeyOf(uint256 projectId, address terminalToken) external view returns (PoolKey memory key);
94
93
 
95
- /// @notice The project token address for a given project.
94
+ /// @notice The address of each project's token.
96
95
  /// @param projectId The ID of the project.
97
- /// @return projectTokenOf The project token address.
96
+ /// @return projectTokenOf The project's token address.
98
97
  function projectTokenOf(uint256 projectId) external view returns (address projectTokenOf);
99
98
 
100
- /// @notice The TWAP window for a given project.
99
+ /// @notice The TWAP window for the given project.
101
100
  /// @param projectId The ID of the project.
102
101
  /// @return window The TWAP window in seconds.
103
102
  function twapWindowOf(uint256 projectId) external view returns (uint256 window);
@@ -115,8 +114,8 @@ interface IJBBuybackHook is IJBPayHook, IJBRulesetDataHook {
115
114
  )
116
115
  external;
117
116
 
118
- /// @notice Set the TWAP window for a given project.
119
- /// @param projectId The ID of the project to set the TWAP window for.
120
- /// @param newWindow The new TWAP window in seconds.
117
+ /// @notice Change the TWAP window for a project.
118
+ /// @param projectId The ID of the project to set the TWAP window of.
119
+ /// @param newWindow The new TWAP window.
121
120
  function setTwapWindowOf(uint256 projectId, uint256 newWindow) external;
122
121
  }
@@ -4,9 +4,8 @@ pragma solidity ^0.8.0;
4
4
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
5
5
  import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
6
6
 
7
- /// @notice A registry that manages which buyback hooks are used by projects.
8
7
  interface IJBBuybackHookRegistry is IJBRulesetDataHook {
9
- /// @notice Emitted when a hook is allowed to be used by projects.
8
+ /// @notice Emitted when a hook is allowed for use by projects.
10
9
  /// @param hook The hook that was allowed.
11
10
  event JBBuybackHookRegistry_AllowHook(IJBRulesetDataHook hook);
12
11
 
@@ -14,7 +13,7 @@ interface IJBBuybackHookRegistry is IJBRulesetDataHook {
14
13
  /// @param hook The hook that was disallowed.
15
14
  event JBBuybackHookRegistry_DisallowHook(IJBRulesetDataHook hook);
16
15
 
17
- /// @notice Emitted when the hook for a project is locked.
16
+ /// @notice Emitted when a project's hook is locked, preventing it from being changed.
18
17
  /// @param projectId The ID of the project whose hook was locked.
19
18
  event JBBuybackHookRegistry_LockHook(uint256 projectId);
20
19
 
@@ -27,6 +26,10 @@ interface IJBBuybackHookRegistry is IJBRulesetDataHook {
27
26
  /// @param hook The hook that was set.
28
27
  event JBBuybackHookRegistry_SetHook(uint256 indexed projectId, IJBRulesetDataHook hook);
29
28
 
29
+ /// @notice The project registry.
30
+ /// @return The projects contract.
31
+ function PROJECTS() external view returns (IJBProjects);
32
+
30
33
  /// @notice The default hook used when a project has not set a specific hook.
31
34
  /// @return The default data hook.
32
35
  function defaultHook() external view returns (IJBRulesetDataHook);
@@ -46,10 +49,6 @@ interface IJBBuybackHookRegistry is IJBRulesetDataHook {
46
49
  /// @return Whether the hook is allowed.
47
50
  function isHookAllowed(IJBRulesetDataHook hook) external view returns (bool);
48
51
 
49
- /// @notice The project registry.
50
- /// @return The projects contract.
51
- function PROJECTS() external view returns (IJBProjects);
52
-
53
52
  /// @notice Allow a hook to be used by projects.
54
53
  /// @param hook The hook to allow.
55
54
  function allowHook(IJBRulesetDataHook hook) external;