@bananapus/distributor-v6 0.0.3
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/.github/pull_request_template.md +33 -0
- package/.github/workflows/lint.yml +19 -0
- package/.github/workflows/publish.yml +19 -0
- package/.github/workflows/slither.yml +23 -0
- package/.github/workflows/test.yml +26 -0
- package/.gitmodules +3 -0
- package/ADMINISTRATION.md +65 -0
- package/ARCHITECTURE.md +89 -0
- package/AUDIT_INSTRUCTIONS.md +52 -0
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/RISKS.md +78 -0
- package/SKILLS.md +36 -0
- package/USER_JOURNEYS.md +120 -0
- package/foundry.toml +22 -0
- package/package.json +29 -0
- package/references/operations.md +25 -0
- package/references/runtime.md +36 -0
- package/remappings.txt +1 -0
- package/script/Deploy.s.sol +23 -0
- package/slither-ci.config.json +10 -0
- package/src/JB721Distributor.sol +180 -0
- package/src/JBDistributor.sol +563 -0
- package/src/JBTokenDistributor.sol +160 -0
- package/src/interfaces/IJB721Distributor.sol +15 -0
- package/src/interfaces/IJBDistributor.sol +138 -0
- package/src/interfaces/IJBTokenDistributor.sol +16 -0
- package/src/structs/JBTokenSnapshotData.sol +9 -0
- package/src/structs/JBVestingData.sol +11 -0
- package/test/JB721Distributor.t.sol +1985 -0
- package/test/JBTokenDistributor.t.sol +424 -0
- package/test/invariant/JB721DistributorInvariant.t.sol +410 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
|
5
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
6
|
+
import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
|
|
7
|
+
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
8
|
+
import {JBSplitHookContext} from "@bananapus/core-v6/src/structs/JBSplitHookContext.sol";
|
|
9
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
10
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
11
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
12
|
+
|
|
13
|
+
import {IJBTokenDistributor} from "./interfaces/IJBTokenDistributor.sol";
|
|
14
|
+
import {JBDistributor} from "./JBDistributor.sol";
|
|
15
|
+
|
|
16
|
+
/// @notice A singleton distributor that distributes ERC-20 rewards to IVotes-compatible token stakers with linear
|
|
17
|
+
/// vesting.
|
|
18
|
+
/// @dev Any project can use this distributor by configuring a payout split with
|
|
19
|
+
/// `hook = this contract` and `beneficiary = address(their IVotes token)`.
|
|
20
|
+
/// @dev The stake weight of each staker is their delegated voting power at round start (via `getPastVotes`).
|
|
21
|
+
/// Holders must delegate (even to themselves) to participate. Non-delegated supply stays in pool for future rounds.
|
|
22
|
+
/// @dev Implements `IJBSplitHook` so it can receive tokens directly from Juicebox project payout splits.
|
|
23
|
+
contract JBTokenDistributor is JBDistributor, IJBTokenDistributor {
|
|
24
|
+
using SafeERC20 for IERC20;
|
|
25
|
+
|
|
26
|
+
//*********************************************************************//
|
|
27
|
+
// --------------------------- custom errors ------------------------- //
|
|
28
|
+
//*********************************************************************//
|
|
29
|
+
|
|
30
|
+
/// @notice Thrown when the caller is not a terminal or controller for the project.
|
|
31
|
+
error JBTokenDistributor_Unauthorized();
|
|
32
|
+
|
|
33
|
+
/// @notice Thrown when a tokenId has non-zero upper bits (above 160), which would alias to the same staker address.
|
|
34
|
+
error JBTokenDistributor_InvalidTokenId();
|
|
35
|
+
|
|
36
|
+
//*********************************************************************//
|
|
37
|
+
// ---------------- public immutable stored properties --------------- //
|
|
38
|
+
//*********************************************************************//
|
|
39
|
+
|
|
40
|
+
/// @notice The JB directory used to verify terminal/controller callers.
|
|
41
|
+
IJBDirectory public immutable DIRECTORY;
|
|
42
|
+
|
|
43
|
+
//*********************************************************************//
|
|
44
|
+
// -------------------------- constructor ---------------------------- //
|
|
45
|
+
//*********************************************************************//
|
|
46
|
+
|
|
47
|
+
/// @param directory The JB directory used to verify terminal/controller callers.
|
|
48
|
+
/// @param roundDuration_ The minimum amount of time stakers have to claim rewards, specified in blocks.
|
|
49
|
+
/// @param vestingRounds_ The number of rounds until tokens are fully vested.
|
|
50
|
+
constructor(
|
|
51
|
+
IJBDirectory directory,
|
|
52
|
+
uint256 roundDuration_,
|
|
53
|
+
uint256 vestingRounds_
|
|
54
|
+
)
|
|
55
|
+
JBDistributor(roundDuration_, vestingRounds_)
|
|
56
|
+
{
|
|
57
|
+
DIRECTORY = directory;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//*********************************************************************//
|
|
61
|
+
// ---------------------- receive ----------------------------------- //
|
|
62
|
+
//*********************************************************************//
|
|
63
|
+
|
|
64
|
+
/// @notice Allows the contract to receive native ETH (e.g. from payout splits).
|
|
65
|
+
receive() external payable {}
|
|
66
|
+
|
|
67
|
+
//*********************************************************************//
|
|
68
|
+
// ---------------------- external transactions ---------------------- //
|
|
69
|
+
//*********************************************************************//
|
|
70
|
+
|
|
71
|
+
/// @notice Receives tokens from a Juicebox payout split.
|
|
72
|
+
/// @dev Only callable by a terminal or controller for the project in the context.
|
|
73
|
+
/// @dev The hook address (IVotes token) is read from `context.split.beneficiary`.
|
|
74
|
+
/// @param context The split hook context from the terminal or controller.
|
|
75
|
+
function processSplitWith(JBSplitHookContext calldata context) external payable override {
|
|
76
|
+
// Only terminals and controllers for the project can call this.
|
|
77
|
+
if (
|
|
78
|
+
!DIRECTORY.isTerminalOf(context.projectId, IJBTerminal(msg.sender))
|
|
79
|
+
&& DIRECTORY.controllerOf(context.projectId) != IERC165(msg.sender)
|
|
80
|
+
) revert JBTokenDistributor_Unauthorized();
|
|
81
|
+
|
|
82
|
+
// The target hook is the split's beneficiary (the IVotes token address).
|
|
83
|
+
address hook = address(context.split.beneficiary);
|
|
84
|
+
|
|
85
|
+
// If it's not a native-token transfer, check if the caller approved tokens (terminal pattern).
|
|
86
|
+
if (msg.value == 0 && context.amount != 0) {
|
|
87
|
+
uint256 balanceBefore = IERC20(context.token).balanceOf(address(this));
|
|
88
|
+
uint256 allowance = IERC20(context.token).allowance(msg.sender, address(this));
|
|
89
|
+
if (allowance >= context.amount) {
|
|
90
|
+
// Terminal pattern: pull tokens via transferFrom.
|
|
91
|
+
IERC20(context.token).safeTransferFrom(msg.sender, address(this), context.amount);
|
|
92
|
+
}
|
|
93
|
+
// For both terminal and controller paths, credit actual received amount (handles fee-on-transfer).
|
|
94
|
+
_balanceOf[hook][IERC20(context.token)] += IERC20(context.token).balanceOf(address(this)) - balanceBefore;
|
|
95
|
+
} else if (msg.value != 0) {
|
|
96
|
+
// Native ETH: credit actual value received.
|
|
97
|
+
_balanceOf[hook][IERC20(context.token)] += msg.value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//*********************************************************************//
|
|
102
|
+
// -------------------------- public views --------------------------- //
|
|
103
|
+
//*********************************************************************//
|
|
104
|
+
|
|
105
|
+
/// @notice Indicates whether this contract supports the given interface.
|
|
106
|
+
/// @param interfaceId The interface ID to check.
|
|
107
|
+
/// @return A flag indicating support.
|
|
108
|
+
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
|
|
109
|
+
return interfaceId == type(IJBTokenDistributor).interfaceId || interfaceId == type(IJBSplitHook).interfaceId
|
|
110
|
+
|| interfaceId == type(IERC165).interfaceId;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//*********************************************************************//
|
|
114
|
+
// ---------------------- internal transactions ---------------------- //
|
|
115
|
+
//*********************************************************************//
|
|
116
|
+
|
|
117
|
+
/// @notice Check if the account matches the staker address encoded in the tokenId.
|
|
118
|
+
/// @dev tokenId encodes the staker address as `uint256(uint160(stakerAddress))`.
|
|
119
|
+
/// @param hook Unused — access is determined by the tokenId encoding.
|
|
120
|
+
/// @param tokenId The encoded staker address.
|
|
121
|
+
/// @param account The account to check.
|
|
122
|
+
/// @return canClaim True if the account matches the encoded address.
|
|
123
|
+
function _canClaim(address hook, uint256 tokenId, address account) internal pure override returns (bool canClaim) {
|
|
124
|
+
hook; // Silence unused variable warning.
|
|
125
|
+
if (tokenId >> 160 != 0) revert JBTokenDistributor_InvalidTokenId();
|
|
126
|
+
canClaim = address(uint160(tokenId)) == account;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// @notice The delegated voting power of a staker at the current round's start block.
|
|
130
|
+
/// @dev Uses `IVotes.getPastVotes` for checkpointed lookups. The block number is derived from
|
|
131
|
+
/// `roundStartBlock(currentRound())`, which is deterministic within a single transaction and
|
|
132
|
+
/// consistent with the block used for `_totalStake` in `beginVesting`.
|
|
133
|
+
/// @param hook The IVotes-compatible token contract.
|
|
134
|
+
/// @param tokenId The encoded staker address (`uint256(uint160(stakerAddress))`).
|
|
135
|
+
/// @return tokenStakeAmount The delegated voting power at the round start block.
|
|
136
|
+
function _tokenStake(address hook, uint256 tokenId) internal view override returns (uint256 tokenStakeAmount) {
|
|
137
|
+
if (tokenId >> 160 != 0) revert JBTokenDistributor_InvalidTokenId();
|
|
138
|
+
tokenStakeAmount = IVotes(hook).getPastVotes(address(uint160(tokenId)), roundStartBlock(currentRound()));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// @notice The total supply of votes at a specific block.
|
|
142
|
+
/// @dev Uses `IVotes.getPastTotalSupply` for checkpointed lookups.
|
|
143
|
+
/// @param hook The IVotes-compatible token contract.
|
|
144
|
+
/// @param blockNumber The block number to get the total supply at.
|
|
145
|
+
/// @return totalStakedAmount The total supply of votes at the given block.
|
|
146
|
+
function _totalStake(address hook, uint256 blockNumber) internal view override returns (uint256 totalStakedAmount) {
|
|
147
|
+
totalStakedAmount = IVotes(hook).getPastTotalSupply(blockNumber);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// @notice IVotes tokens cannot be "burned" in the NFT sense — always returns false.
|
|
151
|
+
/// @dev `releaseForfeitedRewards` will always revert for this distributor.
|
|
152
|
+
/// @param hook Unused.
|
|
153
|
+
/// @param tokenId Unused.
|
|
154
|
+
/// @return tokenWasBurned Always false.
|
|
155
|
+
function _tokenBurned(address hook, uint256 tokenId) internal pure override returns (bool tokenWasBurned) {
|
|
156
|
+
hook;
|
|
157
|
+
tokenId;
|
|
158
|
+
tokenWasBurned = false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
5
|
+
import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
|
|
6
|
+
|
|
7
|
+
import {IJBDistributor} from "./IJBDistributor.sol";
|
|
8
|
+
|
|
9
|
+
/// @notice A singleton distributor that distributes ERC-20 rewards to JB 721 NFT stakers with linear vesting.
|
|
10
|
+
/// @dev Also implements `IJBSplitHook` to receive tokens from payout splits.
|
|
11
|
+
/// @dev Projects configure their split with `hook = distributor` and `beneficiary = their721Hook`.
|
|
12
|
+
interface IJB721Distributor is IJBDistributor, IJBSplitHook {
|
|
13
|
+
/// @notice The JB directory used to verify terminal/controller callers.
|
|
14
|
+
function DIRECTORY() external view returns (IJBDirectory);
|
|
15
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
5
|
+
|
|
6
|
+
import {JBTokenSnapshotData} from "../structs/JBTokenSnapshotData.sol";
|
|
7
|
+
|
|
8
|
+
/// @notice A contract managing distributions of tokens to be claimed and vested by stakers of any other token.
|
|
9
|
+
interface IJBDistributor {
|
|
10
|
+
//*********************************************************************//
|
|
11
|
+
// -------------------------------- events --------------------------- //
|
|
12
|
+
//*********************************************************************//
|
|
13
|
+
|
|
14
|
+
/// @notice Emitted when a staker begins vesting tokens.
|
|
15
|
+
/// @param hook The hook whose stakers are vesting.
|
|
16
|
+
/// @param tokenId The ID of the staked token that is claiming.
|
|
17
|
+
/// @param token The address of the token being vested.
|
|
18
|
+
/// @param amount The amount of tokens being vested.
|
|
19
|
+
/// @param vestingReleaseRound The round at which the tokens will be fully released.
|
|
20
|
+
event Claimed(
|
|
21
|
+
address indexed hook, uint256 indexed tokenId, IERC20 token, uint256 amount, uint256 vestingReleaseRound
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
/// @notice Emitted when vested tokens are collected.
|
|
25
|
+
/// @param hook The hook whose stakers are collecting.
|
|
26
|
+
/// @param tokenId The ID of the staked token collecting.
|
|
27
|
+
/// @param token The address of the token being collected.
|
|
28
|
+
/// @param amount The amount of tokens collected.
|
|
29
|
+
/// @param vestingReleaseRound The round at which the tokens will be fully released.
|
|
30
|
+
event Collected(
|
|
31
|
+
address indexed hook, uint256 indexed tokenId, IERC20 token, uint256 amount, uint256 vestingReleaseRound
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/// @notice Emitted when a snapshot is created for a round.
|
|
35
|
+
/// @param hook The hook the snapshot is for.
|
|
36
|
+
/// @param round The round the snapshot was created for.
|
|
37
|
+
/// @param token The token the snapshot is of.
|
|
38
|
+
/// @param balance The token balance at the time of the snapshot.
|
|
39
|
+
/// @param vestingAmount The amount of tokens vesting at the time of the snapshot.
|
|
40
|
+
event SnapshotCreated(
|
|
41
|
+
address indexed hook, uint256 indexed round, IERC20 indexed token, uint256 balance, uint256 vestingAmount
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
//*********************************************************************//
|
|
45
|
+
// ----------------------------- views ------------------------------- //
|
|
46
|
+
//*********************************************************************//
|
|
47
|
+
|
|
48
|
+
/// @notice The minimum amount of time stakers have to claim rewards, specified in blocks.
|
|
49
|
+
function roundDuration() external view returns (uint256);
|
|
50
|
+
|
|
51
|
+
/// @notice The number of rounds until tokens are fully vested.
|
|
52
|
+
function vestingRounds() external view returns (uint256);
|
|
53
|
+
|
|
54
|
+
/// @notice The number of the current round.
|
|
55
|
+
function currentRound() external view returns (uint256);
|
|
56
|
+
|
|
57
|
+
/// @notice The block at which a round started.
|
|
58
|
+
/// @param round The round to get the start block of.
|
|
59
|
+
function roundStartBlock(uint256 round) external view returns (uint256);
|
|
60
|
+
|
|
61
|
+
/// @notice The balance of a token held for a specific hook's stakers.
|
|
62
|
+
/// @param hook The hook whose balance to check.
|
|
63
|
+
/// @param token The token to check the balance of.
|
|
64
|
+
function balanceOf(address hook, IERC20 token) external view returns (uint256);
|
|
65
|
+
|
|
66
|
+
/// @notice Calculate how much of the token has been claimed for the given tokenId.
|
|
67
|
+
/// @param hook The hook the tokenId belongs to.
|
|
68
|
+
/// @param tokenId The ID of the token to calculate the token amount for.
|
|
69
|
+
/// @param token The address of the token being claimed.
|
|
70
|
+
function claimedFor(address hook, uint256 tokenId, IERC20 token) external view returns (uint256);
|
|
71
|
+
|
|
72
|
+
/// @notice Calculate how much of the token is currently ready to be collected for the given tokenId.
|
|
73
|
+
/// @param hook The hook the tokenId belongs to.
|
|
74
|
+
/// @param tokenId The ID of the token to calculate the token amount for.
|
|
75
|
+
/// @param token The address of the token being claimed.
|
|
76
|
+
function collectableFor(address hook, uint256 tokenId, IERC20 token) external view returns (uint256);
|
|
77
|
+
|
|
78
|
+
/// @notice The amount of a token that is currently vesting for a hook's stakers.
|
|
79
|
+
/// @param hook The hook whose vesting amount to check.
|
|
80
|
+
/// @param token The address of the token that is vesting.
|
|
81
|
+
function totalVestingAmountOf(address hook, IERC20 token) external view returns (uint256);
|
|
82
|
+
|
|
83
|
+
/// @notice The snapshot data of the token information for each round.
|
|
84
|
+
/// @param hook The hook the snapshot is for.
|
|
85
|
+
/// @param token The address of the token being claimed and vested.
|
|
86
|
+
/// @param round The round to which the data applies.
|
|
87
|
+
function snapshotAtRoundOf(
|
|
88
|
+
address hook,
|
|
89
|
+
IERC20 token,
|
|
90
|
+
uint256 round
|
|
91
|
+
)
|
|
92
|
+
external
|
|
93
|
+
view
|
|
94
|
+
returns (JBTokenSnapshotData memory);
|
|
95
|
+
|
|
96
|
+
//*********************************************************************//
|
|
97
|
+
// ---------------------------- transactions ------------------------- //
|
|
98
|
+
//*********************************************************************//
|
|
99
|
+
|
|
100
|
+
/// @notice Fund the distributor for a specific hook.
|
|
101
|
+
/// @dev For native ETH, send `msg.value` and pass `IERC20(NATIVE_TOKEN)` as the token.
|
|
102
|
+
/// @param hook The hook to fund.
|
|
103
|
+
/// @param token The token to fund with.
|
|
104
|
+
/// @param amount The amount to fund.
|
|
105
|
+
function fund(address hook, IERC20 token, uint256 amount) external payable;
|
|
106
|
+
|
|
107
|
+
/// @notice Claims tokens and begins vesting.
|
|
108
|
+
/// @param hook The hook whose stakers are vesting.
|
|
109
|
+
/// @param tokenIds The IDs to claim rewards for.
|
|
110
|
+
/// @param tokens The tokens to claim.
|
|
111
|
+
function beginVesting(address hook, uint256[] calldata tokenIds, IERC20[] calldata tokens) external;
|
|
112
|
+
|
|
113
|
+
/// @notice Collect vested tokens.
|
|
114
|
+
/// @param hook The hook whose stakers are collecting.
|
|
115
|
+
/// @param tokenIds The IDs of the tokens to collect for.
|
|
116
|
+
/// @param tokens The address of the tokens being claimed.
|
|
117
|
+
/// @param beneficiary The recipient of the collected tokens.
|
|
118
|
+
function collectVestedRewards(
|
|
119
|
+
address hook,
|
|
120
|
+
uint256[] calldata tokenIds,
|
|
121
|
+
IERC20[] calldata tokens,
|
|
122
|
+
address beneficiary
|
|
123
|
+
)
|
|
124
|
+
external;
|
|
125
|
+
|
|
126
|
+
/// @notice Release vested rewards for burned tokens.
|
|
127
|
+
/// @param hook The hook whose tokens were burned.
|
|
128
|
+
/// @param tokenIds The IDs of the burned tokens.
|
|
129
|
+
/// @param tokens The address of the tokens being released.
|
|
130
|
+
/// @param beneficiary The recipient of the released tokens.
|
|
131
|
+
function releaseForfeitedRewards(
|
|
132
|
+
address hook,
|
|
133
|
+
uint256[] calldata tokenIds,
|
|
134
|
+
IERC20[] calldata tokens,
|
|
135
|
+
address beneficiary
|
|
136
|
+
)
|
|
137
|
+
external;
|
|
138
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
5
|
+
import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
|
|
6
|
+
|
|
7
|
+
import {IJBDistributor} from "./IJBDistributor.sol";
|
|
8
|
+
|
|
9
|
+
/// @notice A singleton distributor that distributes ERC-20 rewards to IVotes-compatible token stakers with linear
|
|
10
|
+
/// vesting.
|
|
11
|
+
/// @dev Also implements `IJBSplitHook` to receive tokens from payout splits.
|
|
12
|
+
/// @dev Projects configure their split with `hook = distributor` and `beneficiary = their IVotes token`.
|
|
13
|
+
interface IJBTokenDistributor is IJBDistributor, IJBSplitHook {
|
|
14
|
+
/// @notice The JB directory used to verify terminal/controller callers.
|
|
15
|
+
function DIRECTORY() external view returns (IJBDirectory);
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
/// @custom:member balance The token balance at the time of the snapshot.
|
|
5
|
+
/// @custom:member vestingAmount The amount of tokens vesting at the time of the snapshot.
|
|
6
|
+
struct JBTokenSnapshotData {
|
|
7
|
+
uint256 balance;
|
|
8
|
+
uint256 vestingAmount;
|
|
9
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
/// @custom:member releaseRound The round at which the vesting tokens are fully released.
|
|
5
|
+
/// @custom:member amount The original amount of tokens that were claimed.
|
|
6
|
+
/// @custom:member shareClaimed The share of the amount that has already been claimed (out of `MAX_SHARE`).
|
|
7
|
+
struct JBVestingData {
|
|
8
|
+
uint256 releaseRound;
|
|
9
|
+
uint256 amount;
|
|
10
|
+
uint256 shareClaimed;
|
|
11
|
+
}
|