@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 +12 -0
- package/package.json +1 -1
- package/script/Deploy.s.sol +1 -1
- package/src/REVOwner.sol +42 -7
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
package/script/Deploy.s.sol
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 !=
|
|
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.
|