@rev-net/core-v6 0.0.33 → 0.0.35

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/USER_JOURNEYS.md CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  ## Repo Purpose
4
4
 
5
- This repo packages autonomous Revnets: staged Juicebox projects whose runtime behavior is intentionally constrained
6
- after launch. It owns deploy-time stage encoding, runtime enforcement, hidden-token mechanics, and lending against
7
- Revnet token exposure. It does not turn the project back into an ordinary owner-governed treasury after deployment.
5
+ This repo packages autonomous Revnets: staged Juicebox projects whose runtime behavior is intentionally constrained after launch. It owns deploy-time stage encoding, runtime enforcement, hidden-token mechanics, and lending against revnet token exposure.
8
6
 
9
7
  ## Primary Actors
10
8
 
@@ -28,135 +26,136 @@ Revnet token exposure. It does not turn the project back into an ordinary owner-
28
26
  **Intent:** deploy a Revnet whose economic envelope is encoded up front and stays bounded afterward.
29
27
 
30
28
  **Preconditions**
29
+
31
30
  - the team knows the stage schedule, issuance behavior, operator envelope, and optional integrations
32
31
  - the team accepts that many choices become expensive or impossible to change later
33
32
 
34
33
  **Main Flow**
34
+
35
35
  1. Use `REVDeployer` with the staged config, split operators, and optional integrations.
36
36
  2. The deployer launches the underlying project and preserves the intended ownership model.
37
37
  3. Stage and auxiliary behavior are committed at launch instead of left to ordinary owner discretion.
38
38
  4. The Revnet can now accept payments and progress through stages under the encoded rules.
39
39
 
40
40
  **Failure Modes**
41
+
41
42
  - teams assume deploy-time parameters can be revisited casually
42
43
  - optional integrations are enabled without auditing their effect on the resulting network
43
44
 
44
45
  **Postconditions**
46
+
45
47
  - the Revnet launches with its long-lived stage envelope encoded up front
46
48
 
47
- ## Journey 2: Participate In The Revnet Across Stage Transitions
49
+ ## Journey 2: Participate Across Stage Transitions
48
50
 
49
51
  **Actor:** participant.
50
52
 
51
53
  **Intent:** buy, hold, and exit Revnet exposure across stage changes.
52
54
 
53
55
  **Preconditions**
56
+
54
57
  - the Revnet is already live
55
58
  - the participant understands active stage parameters can change behavior over time
56
59
 
57
60
  **Main Flow**
61
+
58
62
  1. Pay through the configured terminal or router.
59
63
  2. Let `REVOwner` enforce runtime behavior such as pay handling, delayed exits, and stage-sensitive constraints.
60
- 3. As stages advance, later pays and exits follow the new active parameters while identity stays constant.
64
+ 3. As stages advance, later pays and exits follow the new active parameters while project identity stays constant.
61
65
 
62
66
  **Failure Modes**
67
+
63
68
  - stage parameters are misread as mutable when they are fixed
64
69
  - delayed cash-out behavior is misunderstood
65
- - optional integrations materially change the participant experience in ways the caller ignored
70
+ - optional integrations materially change the participant experience
66
71
 
67
72
  **Postconditions**
68
- - the participant's buys and exits now follow the active stage's constraints
69
73
 
70
- ## Journey 3: Claim Stage-Based Auto-Issuance When It Becomes Available
74
+ - the participant's buys and exits follow the active stage's constraints
75
+
76
+ ## Journey 3: Claim Stage-Based Auto-Issuance
71
77
 
72
78
  **Actor:** auto-issuance beneficiary.
73
79
 
74
80
  **Intent:** claim stage-specific issuance only when it is actually live.
75
81
 
76
82
  **Preconditions**
83
+
77
84
  - the Revnet was deployed with auto-issuance allocations
78
85
  - the target stage has started
79
86
 
80
87
  **Main Flow**
88
+
81
89
  1. Check `amountToAutoIssue(...)`.
82
90
  2. Call `autoIssueFor(...)` once the stage is active.
83
91
  3. The stored allocation is consumed and cannot be claimed twice.
84
92
 
85
93
  **Failure Modes**
94
+
86
95
  - callers try to claim before the stage is active
87
96
  - reviewers assume auto-issuance is a generic mint path rather than a bounded stage allocation
88
97
 
89
98
  **Postconditions**
90
- - the stage allocation is either claimed once or remains reserved until it becomes valid
91
99
 
92
- ## Journey 3a: Hide Tokens To Increase Cash-Out Value For Remaining Holders
100
+ - the stage allocation is either claimed once or remains reserved until valid
101
+
102
+ ## Journey 4: Hide Tokens To Change Visible Supply
93
103
 
94
104
  **Actor:** holder or authorized operator.
95
105
 
96
106
  **Intent:** remove tokens from visible supply temporarily and later restore them.
97
107
 
98
108
  **Preconditions**
99
- - the holder granted the required permissions
109
+
110
+ - the holder granted `BURN_TOKENS` to `REVHiddenTokens`
111
+ - either the holder has been allowlisted for hidden-token actions, or the caller is the project owner / an operator with `HIDE_TOKENS`
100
112
  - the holder accepts the supply and collateral implications of hiding tokens
101
113
 
102
114
  **Main Flow**
115
+
103
116
  1. Grant `BURN_TOKENS` to `REVHiddenTokens`.
104
- 2. Call `hideTokensOf(...)` to burn tokens and track the hidden balance.
105
- 3. The lower visible supply changes per-token cash-out value.
106
- 4. Later call `revealTokensOf(...)` to re-mint hidden tokens.
117
+ 2. An operator calls `setTokenHidingAllowedFor(...)` to allow the holder to hide their own tokens.
118
+ 3. The holder, project owner, or a `HIDE_TOKENS` operator calls `hideTokensOf(...)` to burn tokens and track the hidden balance.
119
+ 4. The lower visible supply changes per-token cash-out value.
120
+ 5. Later, the holder calls `revealTokensOf(...)` to remint hidden tokens back to themselves.
107
121
 
108
122
  **Failure Modes**
123
+
109
124
  - more tokens are revealed than were hidden
110
- - holders forget revealed tokens increase visible supply again
125
+ - holders attempt to hide tokens without being allowlisted
126
+ - non-holders attempt to reveal hidden tokens
111
127
  - hidden tokens are assumed to remain usable as loan collateral
112
128
 
113
129
  **Postconditions**
130
+
114
131
  - visible supply is reduced or restored according to the holder's hidden-token state
115
132
 
116
- ## Journey 4: Borrow Against Revnet Tokens Instead Of Selling Them
133
+ ## Journey 5: Borrow Against Revnet Tokens Instead Of Selling Them
117
134
 
118
135
  **Actor:** holder or delegated loan operator.
119
136
 
120
137
  **Intent:** borrow against Revnet exposure instead of selling it.
121
138
 
122
139
  **Preconditions**
140
+
123
141
  - the holder has eligible Revnet token exposure
124
142
  - the holder trusts any delegated operator with `OPEN_LOAN`
125
143
 
126
144
  **Main Flow**
145
+
127
146
  1. Interact with `REVLoans` using eligible token exposure as collateral.
128
- 2. The system burns the collateralized token exposure and mints a loan NFT.
147
+ 2. The system burns the collateralized exposure and mints a loan NFT.
129
148
  3. Borrowed value is issued under live Revnet economics.
130
149
  4. The borrower can later repay, reallocate, transfer, or face liquidation.
131
150
 
132
151
  **Failure Modes**
152
+
133
153
  - delegated operators redirect value in ways the holder did not intend
134
154
  - reviewers model the loan system as escrowed collateral when it is burned-collateral lending
135
155
 
136
156
  **Postconditions**
137
- - collateralized exposure is transformed into a live loan position under Revnet economics
138
-
139
- ## Journey 5: Repay, Transfer, Or Liquidate A Loan Position
140
-
141
- **Actor:** borrower, loan owner, or liquidator.
142
157
 
143
- **Intent:** change or settle an existing loan position.
144
-
145
- **Preconditions**
146
- - a loan already exists
147
- - the actor has the rights or economic incentives required for the chosen path
148
-
149
- **Main Flow**
150
- 1. Repay to burn debt and restore the collateralized exposure.
151
- 2. Transfer the loan NFT if ownership of the debt position should move.
152
- 3. Liquidate if the encoded conditions permit it.
153
-
154
- **Failure Modes**
155
- - cross-ruleset behavior is misread
156
- - zero-value or sourced-versus-unsourced paths are handled incorrectly by integrations
157
-
158
- **Postconditions**
159
- - the loan position is repaid, transferred, or liquidated according to the chosen path
158
+ - collateralized exposure becomes a live loan position under Revnet economics
160
159
 
161
160
  ## Journey 6: Operate Inside The Bounded Post-Launch Control Envelope
162
161
 
@@ -165,50 +164,32 @@ Revnet token exposure. It does not turn the project back into an ordinary owner-
165
164
  **Intent:** use the sanctioned post-launch controls without violating the autonomous model.
166
165
 
167
166
  **Preconditions**
167
+
168
168
  - the Revnet is live
169
169
  - the operator knows exactly which controls the deployment left available
170
170
 
171
171
  **Main Flow**
172
+
172
173
  1. Review what `REVDeployer` allowed.
173
174
  2. Use only those sanctioned surfaces.
174
175
  3. Audit cross-package behavior whenever optional integrations are enabled.
175
176
 
176
177
  **Failure Modes**
178
+
177
179
  - operators behave as though the Revnet were a normal owner-governed project
178
180
  - reviewers inspect controls in isolation and miss integrated runtime behavior
179
181
 
180
182
  **Postconditions**
181
- - post-launch control remains inside the bounded envelope the deployment left available
182
183
 
183
- ## Journey 7: Receive Cross-Chain Payments With Correct Hook Routing
184
-
185
- **Actor:** remote participant or integrator using suckers.
186
-
187
- **Intent:** preserve the real beneficiary during cross-chain payments into a Revnet.
188
-
189
- **Preconditions**
190
- - the Revnet is configured with suckers and optional hooks that depend on the beneficiary
191
- - relay-beneficiary metadata is provided correctly
192
-
193
- **Main Flow**
194
- 1. The sucker calls `terminal.pay()` with relay-beneficiary metadata.
195
- 2. `REVOwner.beforePayRecordedWith()` resolves the real beneficiary when the payer is a registered sucker.
196
- 3. Downstream hooks observe the remote user rather than the sucker contract.
197
-
198
- **Failure Modes**
199
- - relay metadata is absent or malformed
200
- - downstream hooks accidentally attribute minting or routing to the sucker instead of the user
201
-
202
- **Postconditions**
203
- - cross-chain payments preserve the intended remote beneficiary through the Revnet hook stack
184
+ - post-launch control remains inside the bounded envelope left by deployment
204
185
 
205
186
  ## Trust Boundaries
206
187
 
207
- - `REVDeployer` is trusted for the launch-time envelope the Revnet will live inside
208
- - `REVOwner` is economically binding runtime logic, not advisory middleware
209
- - optional integrations such as 721 hooks, buybacks, router terminals, and suckers materially alter the resulting network
188
+ - this repo is trusted for revnet-specific economics and runtime policy
189
+ - treasury accounting still comes from core
190
+ - optional integrations materially change revnet behavior and must be reviewed together with the local code
210
191
 
211
192
  ## Hand-Offs
212
193
 
213
- - Use [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md) for the underlying project, terminal, and ruleset mechanics that Revnets package and constrain.
214
- - Use [nana-721-hook-v6](../nana-721-hook-v6/USER_JOURNEYS.md), [nana-buyback-hook-v6](../nana-buyback-hook-v6/USER_JOURNEYS.md), and [nana-suckers-v6](../nana-suckers-v6/USER_JOURNEYS.md) when a Revnet deployment enables those optional features.
194
+ - Use [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md) for underlying terminal and project accounting.
195
+ - Use [nana-buyback-hook-v6](../nana-buyback-hook-v6/USER_JOURNEYS.md), [nana-suckers-v6](../nana-suckers-v6/USER_JOURNEYS.md), and [nana-721-hook-v6](../nana-721-hook-v6/USER_JOURNEYS.md) when those integrations are enabled.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rev-net/core-v6",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -102,5 +102,6 @@ Deploy and manage Revnets -- autonomous, unowned Juicebox projects with staged i
102
102
 
103
103
  | Function | Permissions | What it does |
104
104
  |----------|------------|-------------|
105
- | `REVHiddenTokens.hideTokensOf(holder, revnetId, tokenCount)` | Holder or delegated permission | Burns visible tokens, increases hidden balance, and lowers visible supply. |
106
- | `REVHiddenTokens.revealTokensOf(holder, revnetId, tokenCount, beneficiary)` | Holder or delegated permission | Re-mints previously hidden tokens to a beneficiary and reduces hidden balance. |
105
+ | `REVHiddenTokens.hideTokensOf(revnetId, tokenCount, holder)` | Holder only. The holder must either be allowlisted or personally hold `HIDE_TOKENS`. | Burns visible tokens, increases hidden balance, and lowers visible supply. |
106
+ | `REVHiddenTokens.revealTokensOf(revnetId, tokenCount, holder)` | Holder only | Re-mints previously hidden tokens back to the holder and reduces hidden balance. |
107
+ | `REVHiddenTokens.setTokenHidingAllowedFor(revnetId, holder, isAllowed)` | Operator with `HIDE_TOKENS` | Allows or revokes a holder's ability to hide their own tokens. |
@@ -375,7 +375,7 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
375
375
  uint256[] memory customSplitOperatorPermissionIndexes = _extraOperatorPermissions[revnetId];
376
376
 
377
377
  // Make the array that merges the default and custom operator permissions.
378
- allOperatorPermissions = new uint256[](10 + customSplitOperatorPermissionIndexes.length);
378
+ allOperatorPermissions = new uint256[](11 + customSplitOperatorPermissionIndexes.length);
379
379
  allOperatorPermissions[0] = JBPermissionIds.SET_SPLIT_GROUPS;
380
380
  allOperatorPermissions[1] = JBPermissionIds.SET_BUYBACK_POOL;
381
381
  allOperatorPermissions[2] = JBPermissionIds.SET_BUYBACK_TWAP;
@@ -386,10 +386,11 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
386
386
  allOperatorPermissions[7] = JBPermissionIds.SET_ROUTER_TERMINAL;
387
387
  allOperatorPermissions[8] = JBPermissionIds.SET_TOKEN_METADATA;
388
388
  allOperatorPermissions[9] = JBPermissionIds.SIGN_FOR_ERC20;
389
+ allOperatorPermissions[10] = JBPermissionIds.HIDE_TOKENS;
389
390
 
390
391
  // Copy the custom permissions into the array.
391
392
  for (uint256 i; i < customSplitOperatorPermissionIndexes.length;) {
392
- allOperatorPermissions[10 + i] = customSplitOperatorPermissionIndexes[i];
393
+ allOperatorPermissions[11 + i] = customSplitOperatorPermissionIndexes[i];
393
394
  unchecked {
394
395
  ++i;
395
396
  }
@@ -955,6 +956,16 @@ contract REVDeployer is ERC2771Context, IREVDeployer, IERC721Receiver {
955
956
  configuration.description.salt
956
957
  );
957
958
 
959
+ // Include terminal addresses in the hash so cross-chain expansions must use the same terminals.
960
+ // Terminal addresses are deterministic across chains. Accounting contexts are excluded because
961
+ // token addresses (e.g. USDC) legitimately differ per chain.
962
+ for (uint256 i; i < terminalConfigurations.length;) {
963
+ encodedConfiguration = abi.encode(encodedConfiguration, terminalConfigurations[i].terminal);
964
+ unchecked {
965
+ ++i;
966
+ }
967
+ }
968
+
958
969
  // Initialize fund access limit groups for the loan contract.
959
970
  JBFundAccessLimitGroup[] memory fundAccessLimitGroups =
960
971
  _makeLoanFundAccessLimits({terminalConfigurations: terminalConfigurations});
@@ -4,6 +4,7 @@ pragma solidity 0.8.28;
4
4
  import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
5
5
  import {IJBPermissioned} from "@bananapus/core-v6/src/interfaces/IJBPermissioned.sol";
6
6
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
7
+ import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
7
8
  import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
8
9
  import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
9
10
  import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
@@ -11,14 +12,17 @@ import {Context} from "@openzeppelin/contracts/utils/Context.sol";
11
12
 
12
13
  import {IREVHiddenTokens} from "./interfaces/IREVHiddenTokens.sol";
13
14
 
14
- /// @notice Allows revnet token holders to temporarily hide (burn) tokens, excluding them from totalSupply and
15
- /// increasing cash-out value for remaining holders. Hidden tokens can be revealed (re-minted) at any time.
15
+ /// @notice Allows authorized operators to hide (burn) revnet tokens on behalf of holders, excluding them from
16
+ /// governance weight. Hidden tokens are burned from circulating supply, so they also stop contributing to
17
+ /// cash-out and borrow valuations until revealed again.
18
+ /// Hidden tokens can be revealed (re-minted) at any time.
16
19
  contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
17
20
  //*********************************************************************//
18
21
  // --------------------------- custom errors ------------------------- //
19
22
  //*********************************************************************//
20
23
 
21
24
  error REVHiddenTokens_InsufficientHiddenBalance(uint256 hiddenBalance, uint256 requested);
25
+ error REVHiddenTokens_Unauthorized(uint256 revnetId, address caller);
22
26
 
23
27
  //*********************************************************************//
24
28
  // --------------- public immutable stored properties ---------------- //
@@ -27,6 +31,9 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
27
31
  /// @notice The controller that manages revnets using this contract.
28
32
  IJBController public immutable override CONTROLLER;
29
33
 
34
+ /// @notice The projects contract used to resolve revnet owners.
35
+ IJBProjects public immutable PROJECTS;
36
+
30
37
  //*********************************************************************//
31
38
  // --------------------- public stored properties -------------------- //
32
39
  //*********************************************************************//
@@ -40,6 +47,11 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
40
47
  /// @custom:param revnetId The ID of the revnet.
41
48
  mapping(uint256 revnetId => uint256 count) public override totalHiddenOf;
42
49
 
50
+ /// @notice Whether a holder is allowed to hide their own tokens.
51
+ /// @custom:param holder The holder whose tokens are being managed.
52
+ /// @custom:param revnetId The ID of the revnet.
53
+ mapping(address holder => mapping(uint256 revnetId => bool isAllowed)) public override tokenHidingIsAllowedFor;
54
+
43
55
  //*********************************************************************//
44
56
  // -------------------------- constructor ---------------------------- //
45
57
  //*********************************************************************//
@@ -54,20 +66,27 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
54
66
  JBPermissioned(IJBPermissioned(address(controller)).PERMISSIONS())
55
67
  {
56
68
  CONTROLLER = controller;
69
+ PROJECTS = controller.PROJECTS();
57
70
  }
58
71
 
59
- //*********************************************************************//
60
- // --------------------- external transactions ----------------------- //
61
- //*********************************************************************//
62
-
63
72
  /// @notice Hide tokens by burning them and tracking them for later reveal.
73
+ /// @dev The caller must be the holder. Hiding is allowed for holders on the operator-managed allowlist,
74
+ /// and also for holders who are themselves the project owner or an operator with `HIDE_TOKENS`.
64
75
  /// @dev The holder must have granted BURN_TOKENS permission to this contract.
65
76
  /// @param revnetId The ID of the revnet whose tokens to hide.
66
77
  /// @param tokenCount The number of tokens to hide.
67
78
  /// @param holder The address whose tokens to hide.
68
79
  function hideTokensOf(uint256 revnetId, uint256 tokenCount, address holder) external override {
69
- // Only the holder or a permissioned operator can hide tokens.
70
- _requirePermissionFrom({account: holder, projectId: revnetId, permissionId: JBPermissionIds.HIDE_TOKENS});
80
+ address caller = _msgSender();
81
+ if (caller != holder) revert REVHiddenTokens_Unauthorized(revnetId, caller);
82
+
83
+ bool isAllowlistedHolder = tokenHidingIsAllowedFor[holder][revnetId];
84
+ bool isPermissionedOperator =
85
+ _hasPermissionFrom(caller, PROJECTS.ownerOf(revnetId), revnetId, JBPermissionIds.HIDE_TOKENS);
86
+
87
+ if (!isAllowlistedHolder && !isPermissionedOperator) {
88
+ revert REVHiddenTokens_Unauthorized(revnetId, caller);
89
+ }
71
90
 
72
91
  // Increment the holder's hidden balance.
73
92
  hiddenBalanceOf[holder][revnetId] += tokenCount;
@@ -83,24 +102,14 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
83
102
  }
84
103
 
85
104
  /// @notice Reveal previously hidden tokens by re-minting them.
86
- /// @dev A delegated operator (with REVEAL_TOKENS permission) can set `beneficiary` to any address, directing
87
- /// revealed tokens away from the holder. Grant this permission only to trusted operators.
105
+ /// @dev Any holder can reveal their own hidden tokens without special permissions.
106
+ /// Revealed tokens always return to the holder.
88
107
  /// @param revnetId The ID of the revnet whose tokens to reveal.
89
108
  /// @param tokenCount The number of tokens to reveal.
90
- /// @param beneficiary The address that will receive the revealed tokens.
91
109
  /// @param holder The address whose hidden balance to decrement.
92
- function revealTokensOf(
93
- uint256 revnetId,
94
- uint256 tokenCount,
95
- address beneficiary,
96
- address holder
97
- )
98
- external
99
- override
100
- {
101
- // Only the holder or a permissioned operator can reveal tokens.
102
- // Note: the operator controls `beneficiary`, so they can direct revealed tokens to any address.
103
- _requirePermissionFrom({account: holder, projectId: revnetId, permissionId: JBPermissionIds.REVEAL_TOKENS});
110
+ function revealTokensOf(uint256 revnetId, uint256 tokenCount, address holder) external override {
111
+ address caller = _msgSender();
112
+ if (caller != holder) revert REVHiddenTokens_Unauthorized(revnetId, caller);
104
113
 
105
114
  uint256 hidden = hiddenBalanceOf[holder][revnetId];
106
115
 
@@ -118,12 +127,25 @@ contract REVHiddenTokens is ERC2771Context, JBPermissioned, IREVHiddenTokens {
118
127
  // Mint the tokens to the beneficiary without applying the reserved percent.
119
128
  // slither-disable-next-line unused-return,reentrancy-events
120
129
  CONTROLLER.mintTokensOf({
121
- projectId: revnetId, tokenCount: tokenCount, beneficiary: beneficiary, memo: "", useReservedPercent: false
130
+ projectId: revnetId, tokenCount: tokenCount, beneficiary: holder, memo: "", useReservedPercent: false
122
131
  });
123
132
 
124
- emit RevealTokens({
125
- revnetId: revnetId, tokenCount: tokenCount, beneficiary: beneficiary, holder: holder, caller: _msgSender()
133
+ emit RevealTokens({revnetId: revnetId, tokenCount: tokenCount, holder: holder, caller: _msgSender()});
134
+ }
135
+
136
+ /// @notice Allow or disallow a holder to hide their own tokens.
137
+ /// @dev The caller must have `HIDE_TOKENS` permission for the revnet.
138
+ /// @param revnetId The ID of the revnet.
139
+ /// @param holder The holder to update.
140
+ /// @param isAllowed Whether the holder should be allowed.
141
+ function setTokenHidingAllowedFor(uint256 revnetId, address holder, bool isAllowed) external override {
142
+ _requirePermissionFrom({
143
+ account: PROJECTS.ownerOf(revnetId), projectId: revnetId, permissionId: JBPermissionIds.HIDE_TOKENS
126
144
  });
145
+
146
+ tokenHidingIsAllowedFor[holder][revnetId] = isAllowed;
147
+
148
+ emit SetTokenHidingAllowed({revnetId: revnetId, holder: holder, isAllowed: isAllowed});
127
149
  }
128
150
 
129
151
  //*********************************************************************//
package/src/REVLoans.sol CHANGED
@@ -374,8 +374,8 @@ contract REVLoans is ERC721, ERC2771Context, JBPermissioned, Ownable, IREVLoans
374
374
  // collateral. This is by design: loan value tracks the current bonding curve parameters, just as cash-out
375
375
  // value does. Borrowers benefit from decreasing tax rates and are constrained by increasing ones.
376
376
  // Add cross-chain remote values for proportional reclaim.
377
- uint256 omnichainSurplus =
378
- localSurplus + SUCKER_REGISTRY.remoteSurplusOf({projectId: revnetId, decimals: 18, currency: currency});
377
+ uint256 omnichainSurplus = localSurplus
378
+ + SUCKER_REGISTRY.remoteSurplusOf({projectId: revnetId, decimals: decimals, currency: currency});
379
379
  uint256 omnichainSupply = localSupply + SUCKER_REGISTRY.remoteTotalSupplyOf(revnetId);
380
380
  uint256 reclaimable = JBCashOuts.cashOutFrom({
381
381
  surplus: omnichainSurplus,
package/src/REVOwner.sol CHANGED
@@ -184,8 +184,13 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
184
184
  // feeless (e.g. the router terminal routing value between projects), proxy to the buyback hook with our
185
185
  // totalSupply and effectiveSurplusValue.
186
186
  if (context.cashOutTaxRate == 0 || address(feeTerminal) == address(0) || context.beneficiaryIsFeeless) {
187
+ // Build a modified context with cross-chain-adjusted values so the buyback hook sees the global state
188
+ // for its swap-vs-passthrough routing decision.
189
+ JBBeforeCashOutRecordedContext memory routedContext = context;
190
+ routedContext.totalSupply = totalSupply;
191
+ routedContext.surplus.value = effectiveSurplusValue;
187
192
  // slither-disable-next-line unused-return
188
- (cashOutTaxRate, cashOutCount,,, hookSpecifications) = BUYBACK_HOOK.beforeCashOutRecordedWith(context);
193
+ (cashOutTaxRate, cashOutCount,,, hookSpecifications) = BUYBACK_HOOK.beforeCashOutRecordedWith(routedContext);
189
194
  return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
190
195
  }
191
196
 
@@ -227,9 +232,12 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook {
227
232
  feeAmount = context.surplus.value - postFeeReclaimedAmount;
228
233
  }
229
234
 
230
- // Build a context for the buyback hook using only the non-fee token count.
235
+ // Build a context for the buyback hook using the non-fee token count and cross-chain-adjusted values
236
+ // so the buyback hook sees the global state for its swap-vs-passthrough routing decision.
231
237
  JBBeforeCashOutRecordedContext memory buybackHookContext = context;
232
238
  buybackHookContext.cashOutCount = nonFeeCashOutCount;
239
+ buybackHookContext.totalSupply = totalSupply;
240
+ buybackHookContext.surplus.value = effectiveSurplusValue;
233
241
 
234
242
  // Let the buyback hook adjust the cash out parameters and optionally return a hook specification.
235
243
  JBCashOutHookSpecification[] memory buybackHookSpecifications;
@@ -5,6 +5,12 @@ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol
5
5
 
6
6
  /// @notice Manages hiding (burning) and revealing (re-minting) revnet tokens to exclude them from totalSupply.
7
7
  interface IREVHiddenTokens {
8
+ /// @notice Emitted when a holder is allowed or disallowed to hide their own tokens.
9
+ /// @param revnetId The ID of the revnet.
10
+ /// @param holder The holder whose tokens are being allowed or disallowed.
11
+ /// @param isAllowed Whether the holder is allowed.
12
+ event SetTokenHidingAllowed(uint256 indexed revnetId, address indexed holder, bool isAllowed);
13
+
8
14
  /// @notice Emitted when tokens are hidden (burned and tracked for later reveal).
9
15
  /// @param revnetId The ID of the revnet whose tokens are hidden.
10
16
  /// @param tokenCount The number of tokens hidden.
@@ -15,12 +21,9 @@ interface IREVHiddenTokens {
15
21
  /// @notice Emitted when previously hidden tokens are revealed (re-minted).
16
22
  /// @param revnetId The ID of the revnet whose tokens are revealed.
17
23
  /// @param tokenCount The number of tokens revealed.
18
- /// @param beneficiary The address receiving the revealed tokens.
19
24
  /// @param holder The address whose hidden balance is decremented.
20
25
  /// @param caller The address that revealed the tokens.
21
- event RevealTokens(
22
- uint256 indexed revnetId, uint256 tokenCount, address beneficiary, address holder, address caller
23
- );
26
+ event RevealTokens(uint256 indexed revnetId, uint256 tokenCount, address holder, address caller);
24
27
 
25
28
  /// @notice The controller that manages revnets using this contract.
26
29
  /// @return The controller contract.
@@ -37,6 +40,12 @@ interface IREVHiddenTokens {
37
40
  /// @return The total hidden token count.
38
41
  function totalHiddenOf(uint256 revnetId) external view returns (uint256);
39
42
 
43
+ /// @notice Whether a holder is allowed to hide their own tokens.
44
+ /// @param holder The holder whose tokens are being managed.
45
+ /// @param revnetId The ID of the revnet.
46
+ /// @return Whether the holder is allowed.
47
+ function tokenHidingIsAllowedFor(address holder, uint256 revnetId) external view returns (bool);
48
+
40
49
  /// @notice Hide tokens by burning them and tracking them for later reveal.
41
50
  /// @dev The holder must have granted BURN_TOKENS permission to this contract.
42
51
  /// @param revnetId The ID of the revnet whose tokens to hide.
@@ -47,7 +56,13 @@ interface IREVHiddenTokens {
47
56
  /// @notice Reveal previously hidden tokens by re-minting them.
48
57
  /// @param revnetId The ID of the revnet whose tokens to reveal.
49
58
  /// @param tokenCount The number of tokens to reveal.
50
- /// @param beneficiary The address that will receive the revealed tokens.
51
59
  /// @param holder The address whose hidden balance to decrement.
52
- function revealTokensOf(uint256 revnetId, uint256 tokenCount, address beneficiary, address holder) external;
60
+ function revealTokensOf(uint256 revnetId, uint256 tokenCount, address holder) external;
61
+
62
+ /// @notice Allow or disallow a holder to hide their own tokens.
63
+ /// @dev The caller must have `HIDE_TOKENS` permission for the revnet.
64
+ /// @param revnetId The ID of the revnet.
65
+ /// @param holder The holder to update.
66
+ /// @param isAllowed Whether the holder should be allowed.
67
+ function setTokenHidingAllowedFor(uint256 revnetId, address holder, bool isAllowed) external;
53
68
  }