@bananapus/distributor-v6 0.0.28 → 0.0.30

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.
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.28;
3
3
 
4
+ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
4
5
  import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
5
6
  import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
6
7
  import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
@@ -10,6 +11,8 @@ import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
10
11
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11
12
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
12
13
  import {mulDiv} from "@prb/math/src/Common.sol";
14
+ import {IREVLoans} from "@rev-net/core-v6/src/interfaces/IREVLoans.sol";
15
+ import {IREVOwner} from "@rev-net/core-v6/src/interfaces/IREVOwner.sol";
13
16
 
14
17
  import {JBDistributor} from "./JBDistributor.sol";
15
18
  import {IJBDistributor} from "./interfaces/IJBDistributor.sol";
@@ -66,14 +69,22 @@ contract JBTokenDistributor is JBDistributor, IJBTokenDistributor {
66
69
  //*********************************************************************//
67
70
 
68
71
  /// @param directory The JB directory used to verify terminal/controller callers.
72
+ /// @param controller The JB controller used to burn expired or forfeited project-token rewards.
73
+ /// @param revLoans The Revnet loans contract used to borrow against vested revnet rewards.
74
+ /// @param revOwner The REVOwner contract that must own revnet reward token projects.
69
75
  /// @param initialRoundDuration The duration of each round, specified in seconds.
70
76
  /// @param initialVestingRounds The number of rounds until tokens are fully vested.
77
+ /// @param initialClaimDuration The number of seconds claimants have after each reward round becomes claimable.
71
78
  constructor(
72
79
  IJBDirectory directory,
80
+ IJBController controller,
81
+ IREVLoans revLoans,
82
+ IREVOwner revOwner,
73
83
  uint256 initialRoundDuration,
74
- uint256 initialVestingRounds
84
+ uint256 initialVestingRounds,
85
+ uint48 initialClaimDuration
75
86
  )
76
- JBDistributor(initialRoundDuration, initialVestingRounds)
87
+ JBDistributor(controller, revLoans, revOwner, initialRoundDuration, initialVestingRounds, initialClaimDuration)
77
88
  {
78
89
  DIRECTORY = directory;
79
90
  }
@@ -111,7 +122,7 @@ contract JBTokenDistributor is JBDistributor, IJBTokenDistributor {
111
122
 
112
123
  if (msg.value != 0) {
113
124
  // Assign native split proceeds to the current reward round for this IVotes hook.
114
- _recordRewardFunding({hook: hook, token: IERC20(context.token), amount: msg.value, claimDuration: 0});
125
+ _recordRewardFunding({hook: hook, token: IERC20(context.token), amount: msg.value});
115
126
  }
116
127
  } else {
117
128
  // Validate that native ETH is not cross-booked under an ERC-20 token.
@@ -129,7 +140,7 @@ contract JBTokenDistributor is JBDistributor, IJBTokenDistributor {
129
140
  _acceptErc20FundsFrom({token: IERC20(context.token), from: msg.sender, amount: context.amount});
130
141
 
131
142
  // Assign only the amount actually received to this round's reward pot.
132
- _recordRewardFunding({hook: hook, token: IERC20(context.token), amount: delta, claimDuration: 0});
143
+ _recordRewardFunding({hook: hook, token: IERC20(context.token), amount: delta});
133
144
  }
134
145
  }
135
146
 
@@ -206,14 +217,14 @@ contract JBTokenDistributor is JBDistributor, IJBTokenDistributor {
206
217
  /// @param hook The IVotes token whose stakers are claiming.
207
218
  /// @param tokenIds The encoded staker addresses to claim for.
208
219
  /// @param tokens The reward tokens to claim.
209
- function _claimPastRewards(address hook, uint256[] calldata tokenIds, IERC20[] calldata tokens) internal {
220
+ function _claimPastRewards(address hook, uint256[] calldata tokenIds, IERC20[] calldata tokens) internal override {
210
221
  // Round 0 has no completed reward rounds behind it, so nothing can be claimed yet.
211
222
  uint256 round = currentRound();
212
223
  if (round == 0) return;
213
224
 
214
225
  // Current-round funding is excluded. It becomes claimable only after a later round starts.
215
226
  JBClaimContext memory ctx =
216
- JBClaimContext({hook: hook, lastClaimableRound: round - 1, vestingReleaseRound: round + vestingRounds});
227
+ JBClaimContext({hook: hook, lastClaimableRound: round - 1, vestingReleaseRound: round + VESTING_ROUNDS});
217
228
 
218
229
  // Process each reward token independently because each token has its own round funding and claim cursor.
219
230
  for (uint256 i; i < tokens.length;) {
@@ -398,7 +409,7 @@ contract JBTokenDistributor is JBDistributor, IJBTokenDistributor {
398
409
  /// @notice Revert unless the caller is authorized to claim each token ID.
399
410
  /// @param hook The IVotes token whose stakers are claiming.
400
411
  /// @param tokenIds The encoded staker addresses to check.
401
- function _requireCanClaimTokenIds(address hook, uint256[] calldata tokenIds) internal view {
412
+ function _requireCanClaimTokenIds(address hook, uint256[] calldata tokenIds) internal view override {
402
413
  // Each tokenId is an encoded address, so every requested claim must belong to msg.sender.
403
414
  for (uint256 i; i < tokenIds.length;) {
404
415
  if (!_canClaim({hook: hook, tokenId: tokenIds[i], account: msg.sender})) {
@@ -1,9 +1,13 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
+ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
4
5
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import {IREVLoans} from "@rev-net/core-v6/src/interfaces/IREVLoans.sol";
7
+ import {IREVOwner} from "@rev-net/core-v6/src/interfaces/IREVOwner.sol";
5
8
 
6
9
  import {JBTokenSnapshotData} from "../structs/JBTokenSnapshotData.sol";
10
+ import {JBVestingLoan} from "../structs/JBVestingLoan.sol";
7
11
 
8
12
  /// @notice Interface for round-based reward distributors with linear vesting. Stakers claim their share of funded
9
13
  /// reward rounds, and claimed amounts vest linearly over a configurable number of rounds.
@@ -13,6 +17,32 @@ interface IJBDistributor {
13
17
  // -------------------------------- events --------------------------- //
14
18
  //*********************************************************************//
15
19
 
20
+ /// @notice Emitted when vesting revnet rewards are used as Revnet loan collateral.
21
+ /// @param hook The hook whose token ID owns the vesting rewards.
22
+ /// @param tokenId The token ID whose vesting rewards are collateralized.
23
+ /// @param token The revnet reward token used as collateral.
24
+ /// @param loanId The Revnet loan NFT ID held by this distributor.
25
+ /// @param revnetId The revnet whose project token is collateralized.
26
+ /// @param collateralCount The amount of vesting rewards used as collateral.
27
+ /// @param sourceToken The token borrowed from the revnet.
28
+ /// @param minBorrowAmount The minimum amount to borrow.
29
+ /// @param prepaidFeePercent The prepaid fee percent used by the loan.
30
+ /// @param beneficiary The recipient of the borrowed funds.
31
+ /// @param caller The address that opened the loan.
32
+ event BorrowAgainstVesting(
33
+ address indexed hook,
34
+ uint256 indexed tokenId,
35
+ IERC20 indexed token,
36
+ uint256 loanId,
37
+ uint256 revnetId,
38
+ uint256 collateralCount,
39
+ address sourceToken,
40
+ uint256 minBorrowAmount,
41
+ uint256 prepaidFeePercent,
42
+ address beneficiary,
43
+ address caller
44
+ );
45
+
16
46
  /// @notice Emitted when a staker begins vesting tokens.
17
47
  /// @param hook The hook whose stakers are vesting.
18
48
  /// @param tokenId The ID of the staked token that is claiming.
@@ -61,6 +91,38 @@ interface IJBDistributor {
61
91
  address indexed hook, uint256 indexed round, IERC20 indexed token, uint256 amount, address caller
62
92
  );
63
93
 
94
+ /// @notice Emitted when a liquidated distributor-held Revnet loan is written off.
95
+ /// @param hook The hook whose vesting rewards were collateralized.
96
+ /// @param tokenId The token ID whose vesting rewards were collateralized.
97
+ /// @param token The revnet reward token whose collateral was liquidated.
98
+ /// @param loanId The liquidated Revnet loan NFT ID.
99
+ /// @param collateralCount The amount of vesting rewards forfeited by liquidation.
100
+ /// @param caller The address that triggered the write-off.
101
+ event LiquidatedVestingLoanWrittenOff(
102
+ address indexed hook,
103
+ uint256 indexed tokenId,
104
+ IERC20 indexed token,
105
+ uint256 loanId,
106
+ uint256 collateralCount,
107
+ address caller
108
+ );
109
+
110
+ /// @notice Emitted when a distributor-held Revnet loan is repaid and its collateral resumes vesting.
111
+ /// @param loanId The Revnet loan NFT ID that was repaid.
112
+ /// @param paidOffLoanId The paid-off loan ID returned by Revnet loans.
113
+ /// @param token The revnet reward token restored to vesting.
114
+ /// @param collateralCount The amount of vesting rewards restored.
115
+ /// @param repayBorrowAmount The amount repaid, denominated in the loan source token.
116
+ /// @param caller The address that repaid the loan.
117
+ event RepayVestingLoan(
118
+ uint256 indexed loanId,
119
+ uint256 indexed paidOffLoanId,
120
+ IERC20 indexed token,
121
+ uint256 collateralCount,
122
+ uint256 repayBorrowAmount,
123
+ address caller
124
+ );
125
+
64
126
  /// @notice Emitted when a snapshot is created for a round.
65
127
  /// @param hook The hook the snapshot is for.
66
128
  /// @param round The round the snapshot was created for.
@@ -81,11 +143,39 @@ interface IJBDistributor {
81
143
  // ----------------------------- views ------------------------------- //
82
144
  //*********************************************************************//
83
145
 
146
+ /// @notice The number of seconds after a reward round becomes claimable before unclaimed rewards expire.
147
+ /// @dev A zero duration means reward rounds do not expire.
148
+ function CLAIM_DURATION() external view returns (uint48);
149
+
150
+ /// @notice The JB controller used to burn expired or forfeited project-token rewards.
151
+ function CONTROLLER() external view returns (IJBController);
152
+
153
+ /// @notice The duration of each round, specified in seconds.
154
+ function ROUND_DURATION() external view returns (uint256);
155
+
156
+ /// @notice The Revnet loans contract used to borrow against vesting revnet rewards.
157
+ function REV_LOANS() external view returns (IREVLoans);
158
+
159
+ /// @notice The REVOwner contract that must own a reward token's project to enable loan-backed collection.
160
+ function REV_OWNER() external view returns (IREVOwner);
161
+
162
+ /// @notice The starting timestamp of the distributor.
163
+ function STARTING_TIMESTAMP() external view returns (uint256);
164
+
165
+ /// @notice The number of rounds until tokens are fully vested.
166
+ function VESTING_ROUNDS() external view returns (uint256);
167
+
84
168
  /// @notice The balance of a token held for a specific hook's stakers.
85
169
  /// @param hook The hook whose balance to check.
86
170
  /// @param token The token to check the balance of.
87
171
  function balanceOf(address hook, IERC20 token) external view returns (uint256);
88
172
 
173
+ /// @notice The active Revnet loan using one token ID's vesting rewards as collateral.
174
+ /// @param hook The hook the token ID belongs to.
175
+ /// @param tokenId The token ID whose vesting rewards are collateralized.
176
+ /// @param token The reward token used as loan collateral.
177
+ function activeVestingLoanIdOf(address hook, uint256 tokenId, IERC20 token) external view returns (uint256);
178
+
89
179
  /// @notice Calculate how much of the token has been claimed for the given tokenId.
90
180
  /// @param hook The hook the tokenId belongs to.
91
181
  /// @param tokenId The ID of the token to calculate the token amount for.
@@ -101,9 +191,6 @@ interface IJBDistributor {
101
191
  /// @notice The number of the current round.
102
192
  function currentRound() external view returns (uint256);
103
193
 
104
- /// @notice The duration of each round, specified in seconds.
105
- function roundDuration() external view returns (uint256);
106
-
107
194
  /// @notice The block number recorded as the snapshot point for a round.
108
195
  /// @dev Returns 0 if no snapshot block has been recorded yet for this round.
109
196
  /// @param round The round to get the snapshot block of.
@@ -131,8 +218,14 @@ interface IJBDistributor {
131
218
  /// @param token The address of the token that is vesting.
132
219
  function totalVestingAmountOf(address hook, IERC20 token) external view returns (uint256);
133
220
 
134
- /// @notice The number of rounds until tokens are fully vested.
135
- function vestingRounds() external view returns (uint256);
221
+ /// @notice The amount of vesting inventory currently collateralized in Revnet loans.
222
+ /// @param hook The hook whose loaned vesting amount to check.
223
+ /// @param token The reward token used as collateral.
224
+ function totalLoanedVestingAmountOf(address hook, IERC20 token) external view returns (uint256);
225
+
226
+ /// @notice The vesting position collateralized by a Revnet loan.
227
+ /// @param loanId The Revnet loan NFT ID.
228
+ function vestingLoanOf(uint256 loanId) external view returns (JBVestingLoan memory);
136
229
 
137
230
  //*********************************************************************//
138
231
  // ---------------------------- transactions ------------------------- //
@@ -144,6 +237,28 @@ interface IJBDistributor {
144
237
  /// @param tokens The tokens to claim.
145
238
  function beginVesting(address hook, uint256[] calldata tokenIds, IERC20[] calldata tokens) external;
146
239
 
240
+ /// @notice Borrow from a revnet using one token ID's uncollected vesting rewards as collateral.
241
+ /// @param hook The hook whose staker is borrowing against vesting rewards.
242
+ /// @param tokenIds The single token ID to borrow against.
243
+ /// @param tokens The single revnet reward token to collateralize.
244
+ /// @param sourceToken The token to borrow from the revnet.
245
+ /// @param minBorrowAmount The minimum amount to borrow, denominated in `sourceToken`.
246
+ /// @param prepaidFeePercent The fee percent to charge upfront.
247
+ /// @param beneficiary The recipient of the borrowed funds.
248
+ /// @return loanId The Revnet loan NFT ID held by this distributor.
249
+ /// @return collateralCount The amount of vesting rewards used as collateral.
250
+ function borrowAgainstVesting(
251
+ address hook,
252
+ uint256[] calldata tokenIds,
253
+ IERC20[] calldata tokens,
254
+ address sourceToken,
255
+ uint256 minBorrowAmount,
256
+ uint256 prepaidFeePercent,
257
+ address payable beneficiary
258
+ )
259
+ external
260
+ returns (uint256 loanId, uint256 collateralCount);
261
+
147
262
  /// @notice Collect vested tokens.
148
263
  /// @param hook The hook whose stakers are collecting.
149
264
  /// @param tokenIds The IDs of the tokens to collect for.
@@ -164,15 +279,6 @@ interface IJBDistributor {
164
279
  /// @param amount The amount to fund.
165
280
  function fund(address hook, IERC20 token, uint256 amount) external payable;
166
281
 
167
- /// @notice Fund the distributor for a specific hook with expiring rewards.
168
- /// @dev `claimDuration` is measured from the first timestamp at which the funded round can be claimed.
169
- /// @dev A zero duration means the reward round does not expire.
170
- /// @param hook The hook to fund.
171
- /// @param token The token to fund with.
172
- /// @param amount The amount to fund.
173
- /// @param claimDuration The number of seconds claimants have after the round becomes claimable.
174
- function fundWithClaimDuration(address hook, IERC20 token, uint256 amount, uint48 claimDuration) external payable;
175
-
176
282
  /// @notice Burn unclaimed rewards from expired reward rounds.
177
283
  /// @param hook The hook whose expired reward rounds should be burned.
178
284
  /// @param token The reward token to burn.
@@ -183,11 +289,23 @@ interface IJBDistributor {
183
289
  /// @notice Record the snapshot block for the current round. Callable by anyone (keepers, frontends).
184
290
  function poke() external;
185
291
 
186
- /// @notice Release vested rewards for burned tokens.
292
+ /// @notice Repay a distributor-held Revnet loan and restore its collateral to the original vesting schedule.
293
+ /// @param loanId The Revnet loan NFT ID to repay.
294
+ /// @param maxRepayBorrowAmount The maximum amount of source token the caller is willing to repay.
295
+ /// @return paidOffLoanId The paid-off loan ID returned by Revnet loans.
296
+ function repayVestingLoan(
297
+ uint256 loanId,
298
+ uint256 maxRepayBorrowAmount
299
+ )
300
+ external
301
+ payable
302
+ returns (uint256 paidOffLoanId);
303
+
304
+ /// @notice Burn unlocked rewards for burned tokens.
187
305
  /// @param hook The hook whose tokens were burned.
188
306
  /// @param tokenIds The IDs of the burned tokens.
189
- /// @param tokens The addresses of the tokens to release.
190
- /// @param beneficiary The recipient of the released tokens.
307
+ /// @param tokens The addresses of the tokens to burn.
308
+ /// @param beneficiary Unused for forfeiture.
191
309
  function releaseForfeitedRewards(
192
310
  address hook,
193
311
  uint256[] calldata tokenIds,
@@ -195,4 +313,9 @@ interface IJBDistributor {
195
313
  address beneficiary
196
314
  )
197
315
  external;
316
+
317
+ /// @notice Write off a distributor-held Revnet loan after Revnet liquidation permanently destroys its collateral.
318
+ /// @param loanId The liquidated Revnet loan NFT ID.
319
+ /// @return collateralCount The amount of vesting rewards forfeited by liquidation.
320
+ function writeOffLiquidatedVestingLoan(uint256 loanId) external returns (uint256 collateralCount);
198
321
  }
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+
6
+ /// @notice Parameters used while opening a distributor-owned Revnet loan.
7
+ /// @custom:member hook The hook whose token ID owns the vesting rewards.
8
+ /// @custom:member tokenId The token ID whose vesting rewards are collateralized.
9
+ /// @custom:member token The revnet reward token used as loan collateral.
10
+ /// @custom:member sourceToken The token borrowed from the revnet.
11
+ /// @custom:member minBorrowAmount The minimum amount to borrow.
12
+ /// @custom:member prepaidFeePercent The prepaid fee percent used by the loan.
13
+ /// @custom:member beneficiary The recipient of the borrowed funds.
14
+ /// @custom:member revnetId The revnet whose project token is collateralized.
15
+ struct JBBorrowContext {
16
+ address hook;
17
+ uint256 tokenId;
18
+ IERC20 token;
19
+ address sourceToken;
20
+ uint256 minBorrowAmount;
21
+ uint256 prepaidFeePercent;
22
+ address payable beneficiary;
23
+ uint256 revnetId;
24
+ }
@@ -0,0 +1,18 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+
6
+ /// @notice Tracks a Revnet loan collateralized by one token ID's vesting rewards.
7
+ /// @custom:member hook The hook the token ID belongs to.
8
+ /// @custom:member tokenId The token ID whose vesting rewards are collateralized.
9
+ /// @custom:member token The revnet reward token used as loan collateral.
10
+ /// @custom:member vestingDataCount The vesting-entry boundary collateralized by the loan.
11
+ /// @custom:member collateralCount The amount of vesting rewards collateralized.
12
+ struct JBVestingLoan {
13
+ address hook;
14
+ uint256 tokenId;
15
+ IERC20 token;
16
+ uint48 vestingDataCount;
17
+ uint256 collateralCount;
18
+ }