@rev-net/core-v6 0.0.6 → 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.
Files changed (31) hide show
  1. package/SKILLS.md +1 -1
  2. package/docs/book.toml +1 -1
  3. package/docs/src/README.md +151 -54
  4. package/docs/src/SUMMARY.md +0 -2
  5. package/docs/src/src/REVDeployer.sol/contract.REVDeployer.md +148 -117
  6. package/docs/src/src/REVLoans.sol/contract.REVLoans.md +120 -59
  7. package/docs/src/src/interfaces/IREVDeployer.sol/interface.IREVDeployer.md +296 -14
  8. package/docs/src/src/interfaces/IREVLoans.sol/interface.IREVLoans.md +318 -16
  9. package/docs/src/src/structs/README.md +0 -2
  10. package/docs/src/src/structs/REVAutoIssuance.sol/struct.REVAutoIssuance.md +4 -4
  11. package/docs/src/src/structs/REVConfig.sol/struct.REVConfig.md +5 -17
  12. package/docs/src/src/structs/REVCroptopAllowedPost.sol/struct.REVCroptopAllowedPost.md +10 -6
  13. package/docs/src/src/structs/REVDeploy721TiersHookConfig.sol/struct.REVDeploy721TiersHookConfig.md +7 -7
  14. package/docs/src/src/structs/REVDescription.sol/struct.REVDescription.md +5 -5
  15. package/docs/src/src/structs/REVLoan.sol/struct.REVLoan.md +7 -7
  16. package/docs/src/src/structs/REVLoanSource.sol/struct.REVLoanSource.md +3 -3
  17. package/docs/src/src/structs/REVStageConfig.sol/struct.REVStageConfig.md +10 -10
  18. package/docs/src/src/structs/REVSuckerDeploymentConfig.sol/struct.REVSuckerDeploymentConfig.md +3 -3
  19. package/foundry.toml +1 -1
  20. package/package.json +11 -8
  21. package/slither-ci.config.json +1 -1
  22. package/src/REVDeployer.sol +102 -68
  23. package/src/REVLoans.sol +180 -169
  24. package/src/interfaces/IREVDeployer.sol +109 -73
  25. package/src/interfaces/IREVLoans.sol +119 -73
  26. package/test/TestPR27_CEIPattern.t.sol +2 -2
  27. package/test/TestPR32_MixedFixes.t.sol +1 -1
  28. package/test/mock/MockBuybackDataHook.sol +8 -4
  29. package/test/mock/MockBuybackDataHookMintPath.sol +7 -3
  30. package/test/regression/TestI20_CumulativeLoanCounter.t.sol +303 -0
  31. package/test/regression/TestL27_LiquidateGapHandling.t.sol +334 -0
@@ -1,5 +1,5 @@
1
1
  # REVLoan
2
- [Git Source](https://github.com/rev-net/revnet-core-v5/blob/364afaae78a8f60af2b98252dc96af1c2e4760d3/src/structs/REVLoan.sol)
2
+ [Git Source](https://github.com/rev-net/revnet-core-v6/blob/94c003a3a16de2bd012d63cccedd6bd38d21f6e7/src/structs/REVLoan.sol)
3
3
 
4
4
  **Notes:**
5
5
  - member: borrowedAmount The amount that is being borrowed.
@@ -17,12 +17,12 @@
17
17
 
18
18
  ```solidity
19
19
  struct REVLoan {
20
- uint112 amount;
21
- uint112 collateral;
22
- uint48 createdAt;
23
- uint16 prepaidFeePercent;
24
- uint32 prepaidDuration;
25
- REVLoanSource source;
20
+ uint112 amount;
21
+ uint112 collateral;
22
+ uint48 createdAt;
23
+ uint16 prepaidFeePercent;
24
+ uint32 prepaidDuration;
25
+ REVLoanSource source;
26
26
  }
27
27
  ```
28
28
 
@@ -1,5 +1,5 @@
1
1
  # REVLoanSource
2
- [Git Source](https://github.com/rev-net/revnet-core-v5/blob/364afaae78a8f60af2b98252dc96af1c2e4760d3/src/structs/REVLoanSource.sol)
2
+ [Git Source](https://github.com/rev-net/revnet-core-v6/blob/94c003a3a16de2bd012d63cccedd6bd38d21f6e7/src/structs/REVLoanSource.sol)
3
3
 
4
4
  **Notes:**
5
5
  - member: token The token that is being loaned.
@@ -9,8 +9,8 @@
9
9
 
10
10
  ```solidity
11
11
  struct REVLoanSource {
12
- address token;
13
- IJBPayoutTerminal terminal;
12
+ address token;
13
+ IJBPayoutTerminal terminal;
14
14
  }
15
15
  ```
16
16
 
@@ -1,5 +1,5 @@
1
1
  # REVStageConfig
2
- [Git Source](https://github.com/rev-net/revnet-core-v5/blob/364afaae78a8f60af2b98252dc96af1c2e4760d3/src/structs/REVStageConfig.sol)
2
+ [Git Source](https://github.com/rev-net/revnet-core-v6/blob/94c003a3a16de2bd012d63cccedd6bd38d21f6e7/src/structs/REVStageConfig.sol)
3
3
 
4
4
  **Notes:**
5
5
  - member: startsAtOrAfter The timestamp to start a stage at the given rate at or after.
@@ -30,15 +30,15 @@ out.
30
30
 
31
31
  ```solidity
32
32
  struct REVStageConfig {
33
- uint48 startsAtOrAfter;
34
- REVAutoIssuance[] autoIssuances;
35
- uint16 splitPercent;
36
- JBSplit[] splits;
37
- uint112 initialIssuance;
38
- uint32 issuanceCutFrequency;
39
- uint32 issuanceCutPercent;
40
- uint16 cashOutTaxRate;
41
- uint16 extraMetadata;
33
+ uint48 startsAtOrAfter;
34
+ REVAutoIssuance[] autoIssuances;
35
+ uint16 splitPercent;
36
+ JBSplit[] splits;
37
+ uint112 initialIssuance;
38
+ uint32 issuanceCutFrequency;
39
+ uint32 issuanceCutPercent;
40
+ uint16 cashOutTaxRate;
41
+ uint16 extraMetadata;
42
42
  }
43
43
  ```
44
44
 
@@ -1,5 +1,5 @@
1
1
  # REVSuckerDeploymentConfig
2
- [Git Source](https://github.com/rev-net/revnet-core-v5/blob/364afaae78a8f60af2b98252dc96af1c2e4760d3/src/structs/REVSuckerDeploymentConfig.sol)
2
+ [Git Source](https://github.com/rev-net/revnet-core-v6/blob/94c003a3a16de2bd012d63cccedd6bd38d21f6e7/src/structs/REVSuckerDeploymentConfig.sol)
3
3
 
4
4
  **Notes:**
5
5
  - member: deployerConfigurations The information for how to suck tokens to other chains.
@@ -9,8 +9,8 @@
9
9
 
10
10
  ```solidity
11
11
  struct REVSuckerDeploymentConfig {
12
- JBSuckerDeployerConfig[] deployerConfigurations;
13
- bytes32 salt;
12
+ JBSuckerDeployerConfig[] deployerConfigurations;
13
+ bytes32 salt;
14
14
  }
15
15
  ```
16
16
 
package/foundry.toml CHANGED
@@ -6,7 +6,7 @@ libs = ["node_modules", "lib"]
6
6
  fs_permissions = [{ access = "read-write", path = "./"}]
7
7
 
8
8
  [profile.ci_sizes]
9
- optimizer_runs = 200
9
+ optimizer_runs = 500
10
10
  via_ir = true
11
11
 
12
12
  [fuzz]
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/rev-net/revnet-core-v6"
8
8
  },
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
9
12
  "scripts": {
10
13
  "postinstall": "find node_modules -name '*.sol' -type f | xargs grep -l 'pragma solidity 0.8.23;' 2>/dev/null | xargs sed -i '' 's/pragma solidity 0.8.23;/pragma solidity 0.8.26;/g' 2>/dev/null || true",
11
14
  "test": "forge test",
12
- "coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
15
+ "coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
13
16
  "deploy:mainnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
14
17
  "deploy:mainnets:1_1": "source ./.env && npx sphinx propose ./script/Deploy1_1.s.sol --networks mainnets",
15
18
  "deploy:testnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
@@ -17,16 +20,16 @@
17
20
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
18
21
  },
19
22
  "dependencies": {
20
- "@bananapus/721-hook-v6": "^0.0.7",
21
- "@bananapus/buyback-hook-v6": "^0.0.4",
22
- "@bananapus/core-v6": "^0.0.5",
23
- "@bananapus/permission-ids-v6": "^0.0.3",
24
- "@bananapus/suckers-v6": "^0.0.4",
23
+ "@bananapus/721-hook-v6": "^0.0.9",
24
+ "@bananapus/buyback-hook-v6": "^0.0.7",
25
+ "@bananapus/core-v6": "^0.0.10",
26
+ "@bananapus/permission-ids-v6": "^0.0.5",
27
+ "@bananapus/suckers-v6": "^0.0.7",
25
28
  "@bananapus/router-terminal-v6": "^0.0.6",
26
29
  "@croptop/core-v6": "^0.0.6",
27
30
  "@openzeppelin/contracts": "^5.2.0"
28
31
  },
29
32
  "devDependencies": {
30
- "@sphinx-labs/plugins": "^0.33.1"
33
+ "@sphinx-labs/plugins": "^0.33.2"
31
34
  }
32
35
  }
@@ -5,6 +5,6 @@
5
5
  "exclude_medium": false,
6
6
  "exclude_high": false,
7
7
  "disable_color": false,
8
- "filter_paths": "(mocks/|test/|node_modules/)",
8
+ "filter_paths": "(mocks/|test/|node_modules/|lib/)",
9
9
  "legacy_ast": false
10
10
  }
@@ -1,15 +1,18 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.26;
3
3
 
4
+ import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
4
5
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
5
7
  import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
6
8
  import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
7
- import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
8
- import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9
9
  import {mulDiv} from "@prb/math/src/Common.sol";
10
10
  import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
11
11
  import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
12
12
  import {IJBBuybackHook} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHook.sol";
13
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
14
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
15
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
13
16
  import {IJBCashOutHook} from "@bananapus/core-v6/src/interfaces/IJBCashOutHook.sol";
14
17
  import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
15
18
  import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
@@ -26,19 +29,19 @@ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
26
29
  import {JBSplitGroupIds} from "@bananapus/core-v6/src/libraries/JBSplitGroupIds.sol";
27
30
  import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
28
31
  import {JBAfterCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterCashOutRecordedContext.sol";
29
- import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
30
32
  import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
33
+ import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
34
+ import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
31
35
  import {JBCurrencyAmount} from "@bananapus/core-v6/src/structs/JBCurrencyAmount.sol";
32
36
  import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
33
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
34
37
  import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
38
+ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
35
39
  import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
36
40
  import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
37
41
  import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
38
42
  import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
39
43
  import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
40
44
  import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
41
- import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
42
45
  import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
43
46
  import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
44
47
  import {CTPublisher} from "@croptop/core-v6/src/CTPublisher.sol";
@@ -67,11 +70,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
67
70
  error REVDeployer_CashOutsCantBeTurnedOffCompletely(uint256 cashOutTaxRate, uint256 maxCashOutTaxRate);
68
71
  error REVDeployer_MustHaveSplits();
69
72
  error REVDeployer_NothingToAutoIssue();
73
+ error REVDeployer_NothingToBurn();
70
74
  error REVDeployer_RulesetDoesNotAllowDeployingSuckers();
71
75
  error REVDeployer_StageNotStarted(uint256 stageId);
72
76
  error REVDeployer_StagesRequired();
73
77
  error REVDeployer_StageTimesMustIncrease();
74
- error REVDeployer_NothingToBurn();
75
78
  error REVDeployer_Unauthorized(uint256 revnetId, address caller);
76
79
 
77
80
  //*********************************************************************//
@@ -95,6 +98,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
95
98
  /// @dev 10_000 = 1%. This is the standard fee tier for most project token pairs.
96
99
  uint24 public constant DEFAULT_BUYBACK_POOL_FEE = 10_000;
97
100
 
101
+ /// @notice The default tick spacing used when auto-configuring buyback pools.
102
+ /// @dev 60 is the standard tick spacing for the 1% fee tier.
103
+ int24 public constant DEFAULT_BUYBACK_TICK_SPACING = 60;
104
+
98
105
  /// @notice The default TWAP window used when auto-configuring buyback pools.
99
106
  /// @dev 2 days provides robust manipulation resistance.
100
107
  uint32 public constant DEFAULT_BUYBACK_TWAP_WINDOW = 2 days;
@@ -224,49 +231,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
224
231
  // ------------------------- external views -------------------------- //
225
232
  //*********************************************************************//
226
233
 
227
- /// @notice Before a revnet processes an incoming payment, determine the weight and pay hooks to use.
228
- /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a payment.
229
- /// @param context Standard Juicebox payment context. See `JBBeforePayRecordedContext`.
230
- /// @return weight The weight which revnet tokens are minted relative to. This can be used to customize how many
231
- /// tokens get minted by a payment.
232
- /// @return hookSpecifications Amounts (out of what's being paid in) to be sent to pay hooks instead of being paid
233
- /// into the revnet. Useful for automatically routing funds from a treasury as payments come in.
234
- function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
235
- external
236
- view
237
- override
238
- returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
239
- {
240
- // Keep a reference to the specifications provided by the buyback hook.
241
- JBPayHookSpecification[] memory buybackHookSpecifications;
242
-
243
- // Read the weight and specifications from the buyback hook.
244
- (weight, buybackHookSpecifications) = BUYBACK_HOOK.beforePayRecordedWith(context);
245
-
246
- // Keep a reference to the revnet's tiered ERC-721 hook.
247
- IJB721TiersHook tiered721Hook = tiered721HookOf[context.projectId];
248
-
249
- // Is there a tiered ERC-721 hook?
250
- bool usesTiered721Hook = address(tiered721Hook) != address(0);
251
-
252
- // Did the buyback hook return any specifications? (It won't when direct minting is cheaper than swapping.)
253
- bool usesBuybackHook = buybackHookSpecifications.length > 0;
254
-
255
- // Initialize the returned specification array with only the hooks that are present.
256
- hookSpecifications = new JBPayHookSpecification[]((usesTiered721Hook ? 1 : 0) + (usesBuybackHook ? 1 : 0));
257
-
258
- // If we have a tiered ERC-721 hook, add it to the array.
259
- if (usesTiered721Hook) {
260
- hookSpecifications[0] =
261
- JBPayHookSpecification({hook: IJBPayHook(address(tiered721Hook)), amount: 0, metadata: bytes("")});
262
- }
263
-
264
- // Add the buyback hook specification if present.
265
- if (usesBuybackHook) {
266
- hookSpecifications[usesTiered721Hook ? 1 : 0] = buybackHookSpecifications[0];
267
- }
268
- }
269
-
270
234
  /// @notice Determine how a cash out from a revnet should be processed.
271
235
  /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a cash out.
272
236
  /// @dev If a sucker is cashing out, no taxes or fees are imposed.
@@ -339,6 +303,49 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
339
303
  return (context.cashOutTaxRate, nonFeeCashOutCount, context.totalSupply, hookSpecifications);
340
304
  }
341
305
 
306
+ /// @notice Before a revnet processes an incoming payment, determine the weight and pay hooks to use.
307
+ /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a payment.
308
+ /// @param context Standard Juicebox payment context. See `JBBeforePayRecordedContext`.
309
+ /// @return weight The weight which revnet tokens are minted relative to. This can be used to customize how many
310
+ /// tokens get minted by a payment.
311
+ /// @return hookSpecifications Amounts (out of what's being paid in) to be sent to pay hooks instead of being paid
312
+ /// into the revnet. Useful for automatically routing funds from a treasury as payments come in.
313
+ function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
314
+ external
315
+ view
316
+ override
317
+ returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
318
+ {
319
+ // Keep a reference to the specifications provided by the buyback hook.
320
+ JBPayHookSpecification[] memory buybackHookSpecifications;
321
+
322
+ // Read the weight and specifications from the buyback hook.
323
+ (weight, buybackHookSpecifications) = BUYBACK_HOOK.beforePayRecordedWith(context);
324
+
325
+ // Keep a reference to the revnet's tiered ERC-721 hook.
326
+ IJB721TiersHook tiered721Hook = tiered721HookOf[context.projectId];
327
+
328
+ // Is there a tiered ERC-721 hook?
329
+ bool usesTiered721Hook = address(tiered721Hook) != address(0);
330
+
331
+ // Did the buyback hook return any specifications? (It won't when direct minting is cheaper than swapping.)
332
+ bool usesBuybackHook = buybackHookSpecifications.length > 0;
333
+
334
+ // Initialize the returned specification array with only the hooks that are present.
335
+ hookSpecifications = new JBPayHookSpecification[]((usesTiered721Hook ? 1 : 0) + (usesBuybackHook ? 1 : 0));
336
+
337
+ // If we have a tiered ERC-721 hook, add it to the array.
338
+ if (usesTiered721Hook) {
339
+ hookSpecifications[0] =
340
+ JBPayHookSpecification({hook: IJBPayHook(address(tiered721Hook)), amount: 0, metadata: bytes("")});
341
+ }
342
+
343
+ // Add the buyback hook specification if present.
344
+ if (usesBuybackHook) {
345
+ hookSpecifications[usesTiered721Hook ? 1 : 0] = buybackHookSpecifications[0];
346
+ }
347
+ }
348
+
342
349
  /// @notice A flag indicating whether an address has permission to mint a revnet's tokens on-demand.
343
350
  /// @dev Required by the `IJBRulesetDataHook` interface.
344
351
  /// @param revnetId The ID of the revnet to check permissions for.
@@ -459,18 +466,45 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
459
466
  });
460
467
 
461
468
  // Configure a buyback pool for this terminal token with default fee and TWAP window.
462
- // slither-disable-next-line unused-return,calls-loop
463
- IJBBuybackHook(address(BUYBACK_HOOK))
464
- .setPoolFor({
465
- projectId: revnetId,
466
- fee: DEFAULT_BUYBACK_POOL_FEE,
467
- twapWindow: DEFAULT_BUYBACK_TWAP_WINDOW,
468
- terminalToken: accountingContext.token
469
- });
469
+ // slither-disable-next-line calls-loop
470
+ _trySetBuybackPoolFor(revnetId, accountingContext.token);
470
471
  }
471
472
  }
472
473
  }
473
474
 
475
+ /// @notice Try to configure a buyback pool for a terminal token. Silently catches failures (e.g., if the Uniswap V4
476
+ /// pool isn't initialized yet).
477
+ /// @param revnetId The ID of the revnet.
478
+ /// @param terminalToken The terminal token to configure a buyback pool for.
479
+ function _trySetBuybackPoolFor(uint256 revnetId, address terminalToken) internal {
480
+ // Normalize the terminal token (use WETH for native) and get the project token.
481
+ address normalizedTerminalToken = terminalToken == JBConstants.NATIVE_TOKEN
482
+ ? address(IJBBuybackHook(address(BUYBACK_HOOK)).WETH())
483
+ : terminalToken;
484
+ address projectToken = address(CONTROLLER.TOKENS().tokenOf(revnetId));
485
+
486
+ // Sort currencies numerically for the pool key (lower address = currency0).
487
+ (Currency currency0, Currency currency1) = normalizedTerminalToken < projectToken
488
+ ? (Currency.wrap(normalizedTerminalToken), Currency.wrap(projectToken))
489
+ : (Currency.wrap(projectToken), Currency.wrap(normalizedTerminalToken));
490
+
491
+ // Try to set the pool — if the pool isn't initialized in the PoolManager yet, this will revert and be caught.
492
+ try IJBBuybackHook(address(BUYBACK_HOOK))
493
+ .setPoolFor({
494
+ projectId: revnetId,
495
+ poolKey: PoolKey({
496
+ currency0: currency0,
497
+ currency1: currency1,
498
+ fee: DEFAULT_BUYBACK_POOL_FEE,
499
+ tickSpacing: DEFAULT_BUYBACK_TICK_SPACING,
500
+ hooks: IHooks(address(0))
501
+ }),
502
+ twapWindow: DEFAULT_BUYBACK_TWAP_WINDOW,
503
+ terminalToken: terminalToken
504
+ }) {}
505
+ catch {} // Pool may not be initialized yet — that's OK.
506
+ }
507
+
474
508
  /// @notice Make a ruleset configuration for a revnet's stage.
475
509
  /// @param baseCurrency The base currency of the revnet.
476
510
  /// @param stageConfiguration The stage configuration to make a ruleset for.
@@ -696,6 +730,17 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
696
730
  return revnetId;
697
731
  }
698
732
 
733
+ /// @notice Burn any of a revnet's tokens held by this contract.
734
+ /// @dev Project tokens can end up here from reserved token distribution when splits don't sum to 100%.
735
+ /// @param revnetId The ID of the revnet whose tokens should be burned.
736
+ function burnHeldTokensOf(uint256 revnetId) external override {
737
+ uint256 balance = CONTROLLER.TOKENS().totalBalanceOf({holder: address(this), projectId: revnetId});
738
+ if (balance == 0) revert REVDeployer_NothingToBurn();
739
+ CONTROLLER.burnTokensOf({holder: address(this), projectId: revnetId, tokenCount: balance, memo: ""});
740
+ // slither-disable-next-line reentrancy-events
741
+ emit BurnHeldTokens(revnetId, balance, _msgSender());
742
+ }
743
+
699
744
  /// @notice Deploy new suckers for an existing revnet.
700
745
  /// @dev Only the revnet's split operator can deploy new suckers.
701
746
  /// @param revnetId The ID of the revnet to deploy suckers for.
@@ -777,17 +822,6 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
777
822
  return (revnetId, hook);
778
823
  }
779
824
 
780
- /// @notice Burn any of a revnet's tokens held by this contract.
781
- /// @dev Project tokens can end up here from reserved token distribution when splits don't sum to 100%.
782
- /// @param revnetId The ID of the revnet whose tokens should be burned.
783
- function burnHeldTokensOf(uint256 revnetId) external override {
784
- uint256 balance = CONTROLLER.TOKENS().totalBalanceOf({holder: address(this), projectId: revnetId});
785
- if (balance == 0) revert REVDeployer_NothingToBurn();
786
- CONTROLLER.burnTokensOf({holder: address(this), projectId: revnetId, tokenCount: balance, memo: ""});
787
- // slither-disable-next-line reentrancy-events
788
- emit BurnHeldTokens(revnetId, balance, _msgSender());
789
- }
790
-
791
825
  /// @notice Change a revnet's split operator.
792
826
  /// @dev Only a revnet's current split operator can set a new split operator.
793
827
  /// @param revnetId The ID of the revnet to set the split operator of.