@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.
|
|
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.
|
|
22
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
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
|
},
|
package/src/JBBuybackHook.sol
CHANGED
|
@@ -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
|
-
//
|
|
483
|
+
// ------------------------- external views -------------------------- //
|
|
624
484
|
//*********************************************************************//
|
|
625
485
|
|
|
626
|
-
/// @
|
|
627
|
-
|
|
628
|
-
|
|
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
|
|
632
|
-
///
|
|
633
|
-
/// @param
|
|
634
|
-
///
|
|
635
|
-
///
|
|
636
|
-
///
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
510
|
+
override
|
|
511
|
+
returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
|
|
646
512
|
{
|
|
647
|
-
//
|
|
648
|
-
|
|
513
|
+
// Keep a reference to the amount paid in.
|
|
514
|
+
uint256 totalPaid = context.amount.value;
|
|
649
515
|
|
|
650
|
-
//
|
|
651
|
-
|
|
516
|
+
// Keep a reference to the weight.
|
|
517
|
+
weight = context.weight;
|
|
652
518
|
|
|
653
|
-
//
|
|
654
|
-
uint256
|
|
519
|
+
// Keep a reference to the minimum number of tokens expected from the swap.
|
|
520
|
+
uint256 minimumSwapAmountOut;
|
|
655
521
|
|
|
656
|
-
//
|
|
657
|
-
|
|
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
|
-
//
|
|
669
|
-
|
|
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
|
|
672
|
-
if (
|
|
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
|
-
//
|
|
675
|
-
|
|
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
|
|
680
|
-
|
|
539
|
+
// Get a reference to the controller.
|
|
540
|
+
IJBController controller = IJBController(address(DIRECTORY.controllerOf(context.projectId)));
|
|
681
541
|
|
|
682
|
-
//
|
|
683
|
-
|
|
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
|
|
686
|
-
|
|
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
|
-
//
|
|
689
|
-
|
|
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
|
|
693
|
-
function
|
|
694
|
-
return
|
|
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
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
|
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
|
|
19
|
-
/// @param projectId The ID of the project whose tokens
|
|
20
|
-
/// @param leftoverAmount The amount
|
|
21
|
-
/// @param tokenCount The number of tokens minted.
|
|
22
|
-
/// @param caller The address that
|
|
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
|
|
27
|
-
/// @param terminalToken The terminal token
|
|
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
|
|
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
|
|
33
|
-
/// @param projectId The ID of the project
|
|
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
|
|
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
|
|
47
|
-
/// @param projectId The ID of the project whose TWAP window
|
|
48
|
-
/// @param oldWindow The previous TWAP window
|
|
49
|
-
/// @param newWindow The new TWAP window
|
|
50
|
-
/// @param caller The address that
|
|
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
|
|
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
|
|
86
|
-
/// @return The
|
|
84
|
+
/// @notice The wETH contract.
|
|
85
|
+
/// @return The WETH contract.
|
|
87
86
|
function WETH() external view returns (IWETH9);
|
|
88
87
|
|
|
89
|
-
/// @notice The
|
|
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
|
|
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
|
|
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
|
|
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
|
|
119
|
-
/// @param projectId The ID of the project to set the TWAP window
|
|
120
|
-
/// @param newWindow The new TWAP window
|
|
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
|
|
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
|
|
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;
|