@rev-net/core-v6 0.0.51 → 0.0.53

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/CHANGELOG.md CHANGED
@@ -21,6 +21,18 @@ This file describes the verified change from `revnet-core-v5` to the current `re
21
21
  - The v6 test tree is substantially broader than the v5 tree, with dedicated regression, fork, attack, and invariant coverage for loans, cash-outs, split weights, and lifecycle edges.
22
22
  - The repo moved from the v5 `0.8.23` baseline to `0.8.28`.
23
23
 
24
+ ## In-v6 changes
25
+
26
+ ### `0.0.52` — Cap reported surplus on `REVOwner.beforeCashOutRecordedWith` to fit local liquidity
27
+
28
+ PR #149 scaled the fee + reclaim proportionally when the gross global outflow exceeded local terminal liquidity, preserving a nonzero fee. But the data hook still returned the **unscaled** `effectiveSurplusValue` to `JBTerminalStore._cashOutWithDataHook`, which recomputes the beneficiary reclaim as `cashOutFrom(effSurplus, cashOutCount, totalSupply, taxRate)` and caps it at local surplus before adding the fee spec — so `balanceDiff = localSurplus + feeAmount > localSurplus` reverted with `InadequateTerminalStoreBalance`. Omnichain holders could not cash out locally when global surplus dominated.
29
+
30
+ `cashOutFrom` is linear in `surplus`. After the existing PR #149 scaling, `REVOwner` now lowers the reported `effectiveSurplusValue` proportionally so the store's recomputed reclaim is at most `localSurplus - feeAmount`, leaving exact room for the (preserved) fee spec. The buyback hook still receives the full pre-cap global surplus for its routing decision — only the store-facing return is capped.
31
+
32
+ The fee is **never** trimmed or zeroed: that was the regression PR #149 fixed.
33
+
34
+ Integrator impact: omnichain cash-outs that previously reverted with `InadequateTerminalStoreBalance` when local liquidity was the binding cap now settle. The beneficiary receives `localSurplus - feeAmount` and the fee revnet receives `feeAmount`. The user still burns the full `context.cashOutCount` tokens — semantics are the same as the pre-existing local-cap protocol behavior, just now reachable end-to-end.
35
+
24
36
  ## Operator delegation
25
37
 
26
38
  - Added new `JBPermissionIds` for operator delegation in `@bananapus/permission-ids-v6`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.51",
3
+ "version": "0.0.53",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -478,7 +478,7 @@ contract DeployScript is Script, Sphinx {
478
478
  feeRevnetId: FEE_PROJECT_ID,
479
479
  suckerRegistry: suckers.registry,
480
480
  loans: revloans,
481
- deployerBinder: msg.sender
481
+ deployer: msg.sender
482
482
  });
483
483
 
484
484
  // Deploy REVDeployer with the REVLoans, buyback hook, and REVOwner addresses.
package/src/REVOwner.sol CHANGED
@@ -96,7 +96,7 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
96
96
  //*********************************************************************//
97
97
 
98
98
  /// @notice The account allowed to bind the canonical deployer exactly once.
99
- address private immutable _DEPLOYER_BINDER;
99
+ address private immutable _DEPLOYER;
100
100
 
101
101
  //*********************************************************************//
102
102
  // -------------------------- constructor ---------------------------- //
@@ -107,7 +107,7 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
107
107
  /// @param feeRevnetId The Juicebox project ID of the fee revnet.
108
108
  /// @param suckerRegistry The sucker registry.
109
109
  /// @param loans The loan contract.
110
- /// @param deployerBinder The account allowed to bind the canonical deployer via `setDeployer`. Passed explicitly
110
+ /// @param deployer The account allowed to bind the canonical deployer via `setDeployer`. Passed explicitly
111
111
  /// because CREATE2 deployments set `msg.sender` to the factory, not the intended operator.
112
112
  constructor(
113
113
  IJBBuybackHookRegistry buybackHook,
@@ -115,14 +115,14 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
115
115
  uint256 feeRevnetId,
116
116
  IJBSuckerRegistry suckerRegistry,
117
117
  IREVLoans loans,
118
- address deployerBinder
118
+ address deployer
119
119
  ) {
120
120
  BUYBACK_HOOK = buybackHook;
121
121
  DIRECTORY = directory;
122
122
  FEE_REVNET_ID = feeRevnetId;
123
123
  SUCKER_REGISTRY = suckerRegistry;
124
124
  LOANS = loans;
125
- _DEPLOYER_BINDER = deployerBinder;
125
+ _DEPLOYER = deployer;
126
126
  }
127
127
 
128
128
  //*********************************************************************//
@@ -239,6 +239,12 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
239
239
  cashOutTaxRate: context.cashOutTaxRate
240
240
  });
241
241
 
242
+ // Snapshot the unscaled reclaim before the local-liquidity proportional scaling below mutates it. This is
243
+ // what `JBTerminalStore._cashOutWithDataHook` will recompute when it calls
244
+ // `JBCashOuts.cashOutFrom(effectiveSurplusValue, cashOutCount, totalSupply, cashOutTaxRate)` — same inputs,
245
+ // same output. Used to cap the surplus we report to the store so the recompute leaves room for the fee.
246
+ uint256 unscaledReclaim = postFeeReclaimedAmount;
247
+
242
248
  // If the gross outflow exceeds local terminal liquidity, scale reclaim AND fee proportionally so the fee
243
249
  // is preserved instead of being capped to zero when the reclaim alone consumes all local surplus.
244
250
  uint256 grossOutflow = postFeeReclaimedAmount + feeAmount;
@@ -272,6 +278,37 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
272
278
  return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, buybackHookSpecifications);
273
279
  }
274
280
 
281
+ // The store will recompute the beneficiary reclaim as `cashOutFrom(effectiveSurplusValue, cashOutCount,
282
+ // totalSupply, cashOutTaxRate)` and add the fee spec on top. When local liquidity is the binding cap, that
283
+ // sum can exceed local surplus and revert. `cashOutFrom` is linear in `surplus`, so scale the surplus we
284
+ // report so the store-side reclaim is at most `localSurplus - feeAmount`, preserving room for the fee.
285
+ // The fee is NOT scaled here — it was already scaled by the PR #149 block above. Only the store-facing
286
+ // surplus is touched; the buyback hook already received the full pre-cap value for its routing decision.
287
+ //
288
+ // Worked example (local=10, global=100, 500 of 1000 tokens at 50% tax):
289
+ // above this block (PR #149):
290
+ // unscaledReclaim = cashOutFrom(100, 487.5, 1000, 5000) ≈ 36 ETH (global)
291
+ // feeAmount = cashOutFrom(64, 12.5, 512.5, 5000) ≈ 0.78 ETH (global)
292
+ // grossOutflow ≈ 36.78 > 10 → scale both proportionally to local liquidity:
293
+ // postFeeReclaimedAmount *= 10/36.78 ≈ 9.79 ETH
294
+ // feeAmount *= 10/36.78 ≈ 0.214 ETH (preserved, nonzero)
295
+ // this block:
296
+ // reclaimCap = 10 − 0.214 = 9.786 ETH
297
+ // unscaledReclaim (36) > reclaimCap (9.786) → cap the surplus we report:
298
+ // effectiveSurplusValue = 100 × 9.786 / 36 ≈ 27.18 ETH
299
+ // store recompute (linear in surplus):
300
+ // storeReclaim = 36 × (27.18 / 100) ≈ 9.786 ETH
301
+ // balanceDiff = 9.786 + 0.214 = 10 ETH = localSurplus ✓ no revert
302
+ //
303
+ // Underflow safety on `localSurplus − feeAmount`: after PR #149 the relation
304
+ // `feeAmount ≤ localSurplus` holds in both branches — in the scaling branch because
305
+ // `feeAmount ≤ grossOutflow` and the multiplier is `localSurplus / grossOutflow ≤ 1`;
306
+ // in the else branch because `feeAmount ≤ grossOutflow ≤ localSurplus` already.
307
+ uint256 reclaimCap = context.surplus.value - feeAmount;
308
+ if (unscaledReclaim > reclaimCap) {
309
+ effectiveSurplusValue = mulDiv({x: effectiveSurplusValue, y: reclaimCap, denominator: unscaledReclaim});
310
+ }
311
+
275
312
  // Build a hook spec that routes the fee amount to this contract's `afterCashOutRecordedWith` for processing.
276
313
  JBCashOutHookSpecification memory feeSpec = JBCashOutHookSpecification({
277
314
  hook: IJBCashOutHook(address(this)), noop: false, amount: feeAmount, metadata: abi.encode(feeTerminal)
@@ -471,9 +508,7 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
471
508
  /// @param deployer The canonical REVDeployer instance that will manage revnet runtime state.
472
509
  function setDeployer(IREVDeployer deployer) external {
473
510
  // Only the account that deployed this REVOwner may complete the one-time deployer binding.
474
- if (msg.sender != _DEPLOYER_BINDER) {
475
- revert REVOwner_Unauthorized({caller: msg.sender, expectedCaller: _DEPLOYER_BINDER});
476
- }
511
+ if (msg.sender != _DEPLOYER) revert REVOwner_Unauthorized({caller: msg.sender, expectedCaller: _DEPLOYER});
477
512
  // Prevent the deployer binding from being overwritten after initialization.
478
513
  if (address(DEPLOYER) != address(0)) revert REVOwner_AlreadyInitialized({deployer: address(DEPLOYER)});
479
514
  // Store the canonical REVDeployer that is authorized to manage runtime hook state.