@rev-net/core-v6 0.0.15 → 0.0.16

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 (77) hide show
  1. package/ADMINISTRATION.md +5 -1
  2. package/ARCHITECTURE.md +69 -11
  3. package/AUDIT_INSTRUCTIONS.md +90 -7
  4. package/CHANGE_LOG.md +16 -3
  5. package/README.md +32 -7
  6. package/RISKS.md +26 -14
  7. package/SKILLS.md +167 -45
  8. package/STYLE_GUIDE.md +1 -1
  9. package/USER_JOURNEYS.md +20 -6
  10. package/package.json +9 -9
  11. package/script/Deploy.s.sol +80 -16
  12. package/script/helpers/RevnetCoreDeploymentLib.sol +1 -1
  13. package/src/REVDeployer.sol +39 -9
  14. package/src/REVLoans.sol +26 -1
  15. package/test/REV.integrations.t.sol +1 -1
  16. package/test/REVAutoIssuanceFuzz.t.sol +1 -1
  17. package/test/REVDeployerRegressions.t.sol +1 -1
  18. package/test/REVInvincibility.t.sol +1 -1
  19. package/test/REVInvincibilityHandler.sol +1 -1
  20. package/test/REVLifecycle.t.sol +1 -1
  21. package/test/REVLoans.invariants.t.sol +1 -1
  22. package/test/REVLoansAttacks.t.sol +1 -1
  23. package/test/REVLoansFeeRecovery.t.sol +1 -1
  24. package/test/REVLoansFindings.t.sol +1 -1
  25. package/test/REVLoansRegressions.t.sol +1 -1
  26. package/test/REVLoansSourceFeeRecovery.t.sol +1 -1
  27. package/test/REVLoansSourced.t.sol +1 -1
  28. package/test/REVLoansUnSourced.t.sol +1 -1
  29. package/test/TestBurnHeldTokens.t.sol +1 -1
  30. package/test/TestCEIPattern.t.sol +1 -1
  31. package/test/TestCashOutCallerValidation.t.sol +1 -1
  32. package/test/TestConversionDocumentation.t.sol +1 -1
  33. package/test/TestCrossCurrencyReclaim.t.sol +1 -1
  34. package/test/TestCrossSourceReallocation.t.sol +1 -1
  35. package/test/TestERC2771MetaTx.t.sol +1 -1
  36. package/test/TestEmptyBuybackSpecs.t.sol +1 -1
  37. package/test/TestFlashLoanSurplus.t.sol +1 -1
  38. package/test/TestHookArrayOOB.t.sol +1 -1
  39. package/test/TestLiquidationBehavior.t.sol +1 -1
  40. package/test/TestLoanSourceRotation.t.sol +1 -1
  41. package/test/TestLongTailEconomics.t.sol +1 -1
  42. package/test/TestLowFindings.t.sol +1 -1
  43. package/test/TestMixedFixes.t.sol +1 -1
  44. package/test/TestPermit2Signatures.t.sol +1 -1
  45. package/test/TestReallocationSandwich.t.sol +1 -1
  46. package/test/TestRevnetRegressions.t.sol +1 -1
  47. package/test/TestSplitWeightAdjustment.t.sol +1 -1
  48. package/test/TestSplitWeightE2E.t.sol +1 -1
  49. package/test/TestSplitWeightFork.t.sol +9 -10
  50. package/test/TestStageTransitionBorrowable.t.sol +1 -1
  51. package/test/TestSwapTerminalPermission.t.sol +1 -1
  52. package/test/TestUint112Overflow.t.sol +1 -1
  53. package/test/TestZeroRepayment.t.sol +1 -1
  54. package/test/audit/LoanIdOverflowGuard.t.sol +497 -0
  55. package/test/fork/ForkTestBase.sol +8 -11
  56. package/test/fork/TestAutoIssuanceFork.t.sol +1 -1
  57. package/test/fork/TestCashOutFork.t.sol +1 -1
  58. package/test/fork/TestIssuanceDecayFork.t.sol +1 -1
  59. package/test/fork/TestLoanBorrowFork.t.sol +1 -1
  60. package/test/fork/TestLoanCrossRulesetFork.t.sol +1 -1
  61. package/test/fork/TestLoanERC20Fork.t.sol +1 -1
  62. package/test/fork/TestLoanLiquidationFork.t.sol +1 -1
  63. package/test/fork/TestLoanReallocateFork.t.sol +1 -1
  64. package/test/fork/TestLoanRepayFork.t.sol +1 -1
  65. package/test/fork/TestLoanTransferFork.t.sol +1 -1
  66. package/test/fork/TestPermit2PaymentFork.t.sol +1 -1
  67. package/test/fork/TestSplitWeightFork.t.sol +1 -1
  68. package/test/helpers/MaliciousContracts.sol +1 -1
  69. package/test/mock/MockBuybackCashOutRecorder.sol +82 -0
  70. package/test/mock/MockBuybackDataHook.sol +1 -1
  71. package/test/mock/MockBuybackDataHookMintPath.sol +1 -1
  72. package/test/regression/TestBurnPermissionRequired.t.sol +1 -1
  73. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +205 -0
  74. package/test/regression/TestCrossRevnetLiquidation.t.sol +1 -1
  75. package/test/regression/TestCumulativeLoanCounter.t.sol +1 -1
  76. package/test/regression/TestLiquidateGapHandling.t.sol +1 -1
  77. package/test/regression/TestZeroPriceFeed.t.sol +1 -1
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
5
5
  import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
@@ -41,7 +41,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
41
41
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
42
42
  import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
43
43
  import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
44
- import {mulDiv} from "@prb/math/src/Common.sol";
44
+ import {mulDiv, sqrt} from "@prb/math/src/Common.sol";
45
45
 
46
46
  import {IREVDeployer} from "./interfaces/IREVDeployer.sol";
47
47
  import {REVAutoIssuance} from "./structs/REVAutoIssuance.sol";
@@ -235,6 +235,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
235
235
  /// @notice Determine how a cash out from a revnet should be processed.
236
236
  /// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a cash out.
237
237
  /// @dev If a sucker is cashing out, no taxes or fees are imposed.
238
+ /// @dev REVDeployer is intentionally not registered as a feeless address. The protocol fee (2.5%) applies on top
239
+ /// of the rev fee — this is by design. The fee hook spec amount sent to `afterCashOutRecordedWith` will have the
240
+ /// protocol fee deducted by the terminal before reaching this contract, so the rev fee is computed on the
241
+ /// post-protocol-fee amount.
238
242
  /// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
239
243
  /// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
240
244
  /// out.
@@ -317,6 +321,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
317
321
  });
318
322
 
319
323
  // Compose the final hook specifications: buyback spec (if any) + fee spec.
324
+ // NOTE: Only buybackHookSpecifications[0] is used. If the buyback hook returns multiple
325
+ // specs, the additional ones are silently dropped. This is intentional — the buyback hook is
326
+ // expected to return at most one spec for the cash-out buyback swap.
320
327
  if (buybackHookSpecifications.length > 0) {
321
328
  // The buyback hook returned a spec — include it before the fee spec.
322
329
  hookSpecifications = new JBCashOutHookSpecification[](2);
@@ -587,14 +594,33 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
587
594
  }
588
595
  }
589
596
 
590
- /// @notice Try to initialize a Uniswap V4 buyback pool for a terminal token at a generic 1:1 price.
597
+ /// @notice Try to initialize a Uniswap V4 buyback pool for a terminal token at its fair issuance price.
591
598
  /// @dev Called after the ERC-20 token is deployed so the pool can be initialized in the PoolManager.
599
+ /// Computes `sqrtPriceX96` from `initialIssuance` so the pool starts at the same price as the bonding curve.
592
600
  /// Silently catches failures (e.g., if the pool is already initialized).
593
601
  /// @param revnetId The ID of the revnet.
594
602
  /// @param terminalToken The terminal token to initialize a buyback pool for.
595
- function _tryInitializeBuybackPoolFor(uint256 revnetId, address terminalToken) internal {
596
- // Try to initialize the pool at a generic 1:1 sqrtPriceX96 and configure the buyback hook.
597
- // The buyback hook constructs the PoolKey internally from the project token, terminal token, and pool params.
603
+ /// @param initialIssuance The initial issuance rate (project tokens per terminal token, 18 decimals).
604
+ function _tryInitializeBuybackPoolFor(uint256 revnetId, address terminalToken, uint112 initialIssuance) internal {
605
+ uint160 sqrtPriceX96;
606
+
607
+ if (initialIssuance == 0) {
608
+ sqrtPriceX96 = uint160(1 << 96);
609
+ } else {
610
+ address normalizedTerminalToken = terminalToken == JBConstants.NATIVE_TOKEN ? address(0) : terminalToken;
611
+ address projectToken = address(CONTROLLER.TOKENS().tokenOf(revnetId));
612
+
613
+ if (projectToken == address(0) || projectToken == normalizedTerminalToken) {
614
+ sqrtPriceX96 = uint160(1 << 96);
615
+ } else if (normalizedTerminalToken < projectToken) {
616
+ // token0 = terminal, token1 = project → price = issuance / 1e18
617
+ sqrtPriceX96 = uint160(sqrt(mulDiv(uint256(initialIssuance), 1 << 192, 1e18)));
618
+ } else {
619
+ // token0 = project, token1 = terminal → price = 1e18 / issuance
620
+ sqrtPriceX96 = uint160(sqrt(mulDiv(1e18, 1 << 192, uint256(initialIssuance))));
621
+ }
622
+ }
623
+
598
624
  // slither-disable-next-line calls-loop
599
625
  try BUYBACK_HOOK.initializePoolFor({
600
626
  projectId: revnetId,
@@ -602,7 +628,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
602
628
  tickSpacing: DEFAULT_BUYBACK_TICK_SPACING,
603
629
  twapWindow: DEFAULT_BUYBACK_TWAP_WINDOW,
604
630
  terminalToken: terminalToken,
605
- sqrtPriceX96: uint160(1 << 96)
631
+ sqrtPriceX96: sqrtPriceX96
606
632
  }) {}
607
633
  catch {} // Pool may already be initialized — that's OK.
608
634
  }
@@ -860,8 +886,10 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
860
886
 
861
887
  /// @notice Change a revnet's split operator.
862
888
  /// @dev Only a revnet's current split operator can set a new split operator.
889
+ /// @dev Passing `address(0)` as `newSplitOperator` relinquishes operator powers permanently — the permissions
890
+ /// are granted to the zero address (which cannot execute transactions), effectively burning them.
863
891
  /// @param revnetId The ID of the revnet to set the split operator of.
864
- /// @param newSplitOperator The new split operator's address.
892
+ /// @param newSplitOperator The new split operator's address. Use `address(0)` to relinquish operator powers.
865
893
  function setSplitOperatorOf(uint256 revnetId, address newSplitOperator) external override {
866
894
  // Enforce permissions.
867
895
  _checkIfIsSplitOperatorOf({revnetId: revnetId, operator: _msgSender()});
@@ -1089,7 +1117,9 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IJBRulesetDataHook, IJBCas
1089
1117
  for (uint256 j; j < terminalConfiguration.accountingContextsToAccept.length; j++) {
1090
1118
  // slither-disable-next-line calls-loop
1091
1119
  _tryInitializeBuybackPoolFor({
1092
- revnetId: revnetId, terminalToken: terminalConfiguration.accountingContextsToAccept[j].token
1120
+ revnetId: revnetId,
1121
+ terminalToken: terminalConfiguration.accountingContextsToAccept[j].token,
1122
+ initialIssuance: configuration.stageConfigurations[0].initialIssuance
1093
1123
  });
1094
1124
  }
1095
1125
  }
package/src/REVLoans.sol CHANGED
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
5
5
  import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
@@ -438,6 +438,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
438
438
  }
439
439
 
440
440
  /// @notice Generate a ID for a loan given a revnet ID and a loan number within that revnet.
441
+ /// @dev The multiplication and addition can theoretically overflow a uint256 if revnetId or loanNumber are
442
+ /// astronomically large. In practice this is infeasible — it would require 2^256 loans or project IDs, far
443
+ /// beyond any realistic usage. No overflow check is needed.
441
444
  /// @param revnetId The ID of the revnet to generate a loan ID for.
442
445
  /// @param loanNumber The loan number of the loan within the revnet.
443
446
  /// @return The token ID of the 721.
@@ -577,6 +580,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
577
580
  );
578
581
  }
579
582
 
583
+ // Prevent the loan number from exceeding the ID namespace for this revnet.
584
+ if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
585
+
580
586
  // Get a reference to the loan ID.
581
587
  loanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
582
588
 
@@ -987,6 +993,10 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
987
993
  _beforeTransferTo({to: address(feeTerminal), token: loan.source.token, amount: revFeeAmount});
988
994
 
989
995
  // Pay the fee. Send the REV to the beneficiary. If fee payment fails, give the amount back to the borrower.
996
+ // NOTE: When terminal.pay() reverts (e.g. due to a misconfigured terminal or paused payments),
997
+ // the REV fee is refunded to the borrower, resulting in an effectively interest-free loan for the
998
+ // REV fee portion. This is acceptable — it requires a broken/misconfigured fee terminal and the
999
+ // borrower still pays the source fee and protocol fee.
990
1000
  // slither-disable-next-line arbitrary-send-eth,unused-return
991
1001
  try feeTerminal.pay{value: payValue}({
992
1002
  projectId: REV_ID,
@@ -1022,6 +1032,11 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1022
1032
 
1023
1033
  /// @notice Allows the owner of a loan to pay it back, add more, or receive returned collateral no longer necessary
1024
1034
  /// to support the loan.
1035
+ /// @dev CEI ordering note: `totalCollateralOf` is not incremented until `_addCollateralTo` executes,
1036
+ /// which happens after the external calls in `_addTo` (useAllowanceOf, fee payment, transfer). A reentrant
1037
+ /// `borrowFrom` during those calls would see a lower `totalCollateralOf`, potentially passing collateral
1038
+ /// checks that should fail. Practically infeasible — requires an adversarial pay hook on the revnet's own
1039
+ /// terminal that calls back into `borrowFrom`, which is not a realistic deployment configuration.
1025
1040
  /// @param loan The loan being adjusted.
1026
1041
  /// @param revnetId The ID of the revnet the loan is being adjusted in.
1027
1042
  /// @param newBorrowAmount The new amount of the loan, denominated in the token of the source's accounting
@@ -1092,6 +1107,10 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1092
1107
  });
1093
1108
 
1094
1109
  // Pay the fee. If it fails, reclaim the allowance and give the amount back to the borrower.
1110
+ // NOTE: When terminal.pay() reverts (e.g. due to a misconfigured terminal or paused payments),
1111
+ // the source fee is refunded to the borrower, resulting in an effectively interest-free loan for the
1112
+ // source fee portion. This is acceptable — it requires a broken/misconfigured source terminal and
1113
+ // the borrower still pays the REV fee and protocol fee.
1095
1114
  // slither-disable-next-line unused-return,arbitrary-send-eth
1096
1115
  try loan.source.terminal.pay{value: payValue}({
1097
1116
  projectId: revnetId,
@@ -1163,6 +1182,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1163
1182
  revert REVLoans_ReallocatingMoreCollateralThanBorrowedAmountAllows(borrowAmount, loan.amount);
1164
1183
  }
1165
1184
 
1185
+ // Prevent the loan number from exceeding the ID namespace for this revnet.
1186
+ if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
1187
+
1166
1188
  // Get a reference to the replacement loan ID.
1167
1189
  reallocatedLoanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
1168
1190
 
@@ -1292,6 +1314,9 @@ contract REVLoans is ERC721, ERC2771Context, Ownable, IREVLoans {
1292
1314
  return (loanId, paidOffSnapshot);
1293
1315
  } else {
1294
1316
  // Make a new loan with the remaining amount and collateral.
1317
+ // Prevent the loan number from exceeding the ID namespace for this revnet.
1318
+ if (totalLoansBorrowedFor[revnetId] >= _ONE_TRILLION) revert REVLoans_LoanIdOverflow();
1319
+
1295
1320
  // Get a reference to the replacement loan ID.
1296
1321
  uint256 paidOffLoanId = _generateLoanId({revnetId: revnetId, loanNumber: ++totalLoansBorrowedFor[revnetId]});
1297
1322
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -495,13 +495,12 @@ contract TestSplitWeightFork is TestBaseWorkflow {
495
495
  hooks: IHooks(address(0))
496
496
  });
497
497
 
498
- // Pool is already initialized at 1:1 price by REVDeployer during deployment.
499
- // Just add liquidity and mock the oracle.
500
-
501
- // Fund LiquidityHelper with project tokens via JBTokens.mintFor (not deal).
502
- // deal() skips ERC20Votes checkpoints, causing underflow when tokens are burned.
498
+ // Pool is already initialized at fair issuance price by REVDeployer during deployment.
499
+ // At high tick (~69078 for 1000 tokens/ETH), full-range liquidity needs ~32x more project tokens than ETH.
500
+ // Mint 50x project tokens and use a smaller liquidity delta to stay within budget.
501
+ // Use mintFor (not deal) so ERC20Votes checkpoints are updated.
503
502
  vm.prank(address(jbController()));
504
- jbTokens().mintFor(address(liqHelper), revnetId, liquidityTokenAmount);
503
+ jbTokens().mintFor(address(liqHelper), revnetId, liquidityTokenAmount * 50);
505
504
  // Fund with ETH for the native currency side.
506
505
  vm.deal(address(liqHelper), liquidityTokenAmount);
507
506
 
@@ -512,12 +511,12 @@ contract TestSplitWeightFork is TestBaseWorkflow {
512
511
 
513
512
  // Add full-range liquidity.
514
513
  // forge-lint: disable-next-line(unsafe-typecast)
515
- int256 liquidityDelta = int256(liquidityTokenAmount / 2);
514
+ int256 liquidityDelta = int256(liquidityTokenAmount / 50);
516
515
  vm.prank(address(liqHelper));
517
516
  liqHelper.addLiquidity{value: liquidityTokenAmount}(key, TICK_LOWER, TICK_UPPER, liquidityDelta);
518
517
 
519
- // Mock the oracle at address(0) for hookless pools.
520
- _mockOracle(liquidityDelta, 0, uint32(REV_DEPLOYER.DEFAULT_BUYBACK_TWAP_WINDOW()));
518
+ // Mock geomean oracle at tick 69078 (~1000 tokens/ETH, matching INITIAL_ISSUANCE).
519
+ _mockOracle(liquidityDelta, 69_078, uint32(REV_DEPLOYER.DEFAULT_BUYBACK_TWAP_WINDOW()));
521
520
  }
522
521
 
523
522
  /// @notice Mock the IGeomeanOracle at address(0) for hookless pools.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "forge-std/Test.sol";