This repository has been archived by the owner on May 31, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
AdvancedDistributor.sol
209 lines (184 loc) · 7.29 KB
/
AdvancedDistributor.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import '@openzeppelin/contracts/access/Ownable.sol';
import { ERC20Votes, ERC20Permit, ERC20 } from '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol';
import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import { Distributor, DistributionRecord, IERC20 } from './Distributor.sol';
import { IAdjustable } from '../../interfaces/IAdjustable.sol';
import { IVoting } from '../../interfaces/IVoting.sol';
import { Sweepable } from '../../utilities/Sweepable.sol';
import { FairQueue } from '../../utilities/FairQueue.sol';
/**
* @title AdvancedDistributor
* @notice Distributes tokens to beneficiaries with voting-while-vesting and administrative controls.
* The contract owner can control these distribution parameters:
* - the merkle root determining all distribution details
* - adjustments to specific distributions
* - the token being distributed
* - the total amount to distribute
* - the metadata URI
* - the voting power of undistributed tokens
* - the recipient of swept funds
*
* This contract also allows owners to perform several other admin functions
* - updating the contract owner
* - sweeping tokens and native currency to a recipient
*
* This contract tracks beneficiary voting power through an internal ERC20Votes token that cannot be transferred. The
* beneficiary must delegate to an address to use this voting power. Their voting power decreases when the token is claimed.
*
* @dev Updates to the contract must follow these constraints:
* - If a merkle root update alters the total token quantity to distribute across all users, also adjust the total value.
* The DistributionRecord for each beneficiary updated in the merkle root will be incorrect until a claim is executed.
* - If the total changes, make sure to add or withdraw tokens being distributed to match the new total.
*/
abstract contract AdvancedDistributor is
Ownable,
Sweepable,
ERC20Votes,
Distributor,
IAdjustable,
IVoting,
FairQueue
{
using SafeERC20 for IERC20;
uint256 private voteFactor;
constructor(
IERC20 _token,
uint256 _total,
string memory _uri,
uint256 _voteFactor,
uint256 _fractionDenominator,
uint160 _maxDelayTime,
uint160 _salt
)
Distributor(_token, _total, _uri, _fractionDenominator)
ERC20Permit('Internal vote tracker')
ERC20('Internal vote tracker', 'IVT')
Sweepable(payable(msg.sender))
FairQueue(_maxDelayTime, _salt)
{
voteFactor = _voteFactor;
emit SetVoteFactor(voteFactor);
}
/**
* convert a token quantity to a vote quantity
*/
function tokensToVotes(uint256 tokenAmount) private view returns (uint256) {
return (tokenAmount * voteFactor) / fractionDenominator;
}
// Update voting power based on distribution record initialization or claims
function _reconcileVotingPower(address beneficiary) private {
// current potential voting power
uint256 currentVotes = balanceOf(beneficiary);
// correct voting power after initialization, claim, or adjustment
DistributionRecord memory record = records[beneficiary];
uint256 newVotes = record.claimed >= record.total ? 0 : tokensToVotes(record.total - record.claimed);
if (currentVotes > newVotes) {
// reduce voting power through ERC20Votes extension
_burn(beneficiary, currentVotes - newVotes);
} else if (currentVotes < newVotes) {
// increase voting power through ERC20Votes extension
_mint(beneficiary, newVotes - currentVotes);
}
}
function _initializeDistributionRecord(
address beneficiary,
uint256 totalAmount
) internal virtual override {
super._initializeDistributionRecord(beneficiary, totalAmount);
_reconcileVotingPower(beneficiary);
}
function _executeClaim(
address beneficiary,
uint256 totalAmount,
bytes memory data
) internal virtual override returns (uint256 _claimed) {
_claimed = super._executeClaim(beneficiary, totalAmount, data);
_reconcileVotingPower(beneficiary);
}
/**
* @dev Adjust the quantity claimable by a user, overriding the value in the distribution record.
*
* Note: If used in combination with merkle proofs, adjustments to a beneficiary's total could be
* reset by anyone to the value in the merkle leaf at any time. Update the merkle root instead.
*
* Amount is limited to type(uint120).max to allow each DistributionRecord to be packed into a single storage slot.
*/
function adjust(address beneficiary, int256 amount) external onlyOwner {
DistributionRecord memory distributionRecord = records[beneficiary];
require(distributionRecord.initialized, 'must initialize before adjusting');
uint256 diff = uint256(amount > 0 ? amount : -amount);
require(diff < type(uint120).max, 'adjustment > max uint120');
if (amount < 0) {
// decreasing claimable tokens
require(total >= diff, 'decrease greater than distributor total');
require(distributionRecord.total >= diff, 'decrease greater than distributionRecord total');
total -= diff;
records[beneficiary].total -= uint120(diff);
token.safeTransfer(owner(), diff);
} else {
// increasing claimable tokens
total += diff;
records[beneficiary].total += uint120(diff);
}
_reconcileVotingPower(beneficiary);
emit Adjust(beneficiary, amount);
}
function _setToken(IERC20 _token) internal virtual {
require(address(_token) != address(0), 'token is address(0)');
token = _token;
emit SetToken(token);
}
// Set the token being distributed
function setToken(IERC20 _token) external virtual onlyOwner {
_setToken(_token);
}
function _setTotal(uint256 _total) internal virtual {
total = _total;
emit SetTotal(total);
}
// Set the total to distribute
function setTotal(uint256 _total) external virtual onlyOwner {
_setTotal(_total);
}
// Set the distributor metadata URI
function setUri(string memory _uri) external onlyOwner {
uri = _uri;
emit SetUri(uri);
}
// set the recipient of swept funds
function setSweepRecipient(address payable _recipient) external onlyOwner {
_setSweepRecipient(_recipient);
}
function getTotalVotes() external view returns (uint256) {
// supply of internal token used to track voting power
return totalSupply();
}
function getVoteFactor(address) external view returns (uint256) {
return voteFactor;
}
/**
* @notice Set the voting power of undistributed tokens
* @param _voteFactor The voting power multiplier as a fraction of fractionDenominator
* @dev The vote factor can be any integer. If voteFactor / fractionDenominator == 1,
* one unclaimed token provides one vote. If voteFactor / fractionDenominator == 2, one
* unclaimed token counts as two votes.
*/
function setVoteFactor(uint256 _voteFactor) external onlyOwner {
voteFactor = _voteFactor;
emit SetVoteFactor(voteFactor);
}
/**
* @dev the internal token used only for tracking voting power cannot be transferred
*/
function _approve(address, address, uint256) internal pure override {
revert('disabled for voting power');
}
/**
* @dev the internal token used only f or tracking voting power cannot be transferred
*/
function _transfer(address, address, uint256) internal pure override {
revert('disabled for voting power');
}
}