-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathKUMAFeeCollector.sol
More file actions
250 lines (207 loc) · 8.15 KB
/
Copy pathKUMAFeeCollector.sol
File metadata and controls
250 lines (207 loc) · 8.15 KB
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Errors} from "./libraries/Errors.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IKUMAAddressProvider} from "./interfaces/IKUMAAddressProvider.sol";
import {IKUMAFeeCollector} from "./interfaces/IKUMAFeeCollector.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {Roles} from "./libraries/Roles.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
contract KUMAFeeCollector is IKUMAFeeCollector, UUPSUpgradeable, Initializable {
using EnumerableSet for EnumerableSet.AddressSet;
using SafeERC20 for IERC20;
IKUMAAddressProvider private _KUMAAddressProvider;
bytes32 private _riskCategory;
EnumerableSet.AddressSet private _payees;
uint256 private _totalShares;
mapping(address => uint256) private _shares;
modifier onlyManager() {
if (!_KUMAAddressProvider.getAccessController().hasRole(Roles.KUMA_MANAGER_ROLE, msg.sender)) {
revert Errors.ACCESS_CONTROL_ACCOUNT_IS_MISSING_ROLE(msg.sender, Roles.KUMA_MANAGER_ROLE);
}
_;
}
constructor() initializer {}
function initialize(IKUMAAddressProvider KUMAAddressProvider, bytes4 currency, bytes4 country, uint64 term)
external
override
initializer
{
if (address(KUMAAddressProvider) == address(0)) {
revert Errors.CANNOT_SET_TO_ADDRESS_ZERO();
}
if (currency == bytes4(0) || country == bytes4(0) || term == 0) {
revert Errors.WRONG_RISK_CATEGORY();
}
_KUMAAddressProvider = KUMAAddressProvider;
_riskCategory = keccak256(abi.encode(currency, country, term));
emit KUMAAddressProviderSet(address(KUMAAddressProvider));
emit RiskCategorySet(_riskCategory);
}
/**
* @notice Releases the accumulated fee income to the payees.
* @dev Uses _totalShares to calculate correct share.
*/
function release() external override {
IERC20 KIBToken = IERC20(_KUMAAddressProvider.getKIBToken(_riskCategory));
uint256 availableIncome = KIBToken.balanceOf(address(this));
if (availableIncome == 0) {
revert Errors.NO_AVAILABLE_INCOME();
}
if (_payees.length() == 0) {
revert Errors.NO_PAYEES();
}
_release(KIBToken, availableIncome);
}
/**
* @notice Adds a payee.
* @dev Will update totalShares and therefore reduce the relative share of all other payees.
* @dev Will release existing fees before the update.
* @param payee The address of the payee to add.
* @param share The number of shares owned by the payee.
*/
function addPayee(address payee, uint256 share) external override onlyManager {
if (_payees.contains(payee)) {
revert Errors.PAYEE_ALREADY_EXISTS();
}
if (payee == address(0)) {
revert Errors.CANNOT_SET_TO_ADDRESS_ZERO();
}
if (share == 0) {
revert Errors.SHARE_CANNOT_BE_ZERO();
}
_releaseIfAvailableIncome();
_payees.add(payee);
_shares[payee] = share;
_totalShares += share;
emit PayeeAdded(payee, share);
}
/**
* @notice Removes a payee.
* @dev Will update totalShares and therefore increase the relative share of all other payees.
* @dev Will release existing fees before the update.
* @param payee The address of the payee to add.
*/
function removePayee(address payee) external override onlyManager {
if (!_payees.contains(payee)) {
revert Errors.PAYEE_DOES_NOT_EXIST();
}
_releaseIfAvailableIncome();
_payees.remove(payee);
_totalShares -= _shares[payee];
delete _shares[payee];
emit PayeeRemoved(payee);
}
/**
* @notice Updates an existing payee's share.
* @dev Will release existing fees before the update.
* @param payee Payee's address.
* @param share New payee's share.
*/
function updatePayeeShare(address payee, uint256 share) external onlyManager {
if (!_payees.contains(payee)) {
revert Errors.PAYEE_DOES_NOT_EXIST();
}
if (share == 0) {
revert Errors.SHARE_CANNOT_BE_ZERO();
}
_releaseIfAvailableIncome();
uint256 currentShare = _shares[payee];
if (currentShare < share) {
_totalShares += share - currentShare;
} else if (currentShare > share) {
_totalShares -= currentShare - share;
}
_shares[payee] = share;
emit ShareUpdated(payee, share);
}
/**
* @notice Updates the payee configuration to a new one.
* @dev Will release existing fees before the update.
* @param newPayees Array of new payees
* @param newShares Array of shares for each new payee
*/
function changePayees(address[] calldata newPayees, uint256[] calldata newShares) external override onlyManager {
if (newPayees.length != newShares.length) {
revert Errors.PAYEES_AND_SHARES_MISMATCHED(newPayees.length, newShares.length);
}
if (newPayees.length == 0) {
revert Errors.NO_PAYEES();
}
_releaseIfAvailableIncome();
uint256 payeesLength = _payees.length();
if (payeesLength > 0) {
for (uint256 i = payeesLength; i > 0; i--) {
address payee = _payees.at(i - 1);
_payees.remove(payee);
delete _shares[payee];
emit PayeeRemoved(payee);
}
_totalShares = 0;
}
for (uint256 i; i < newPayees.length; i++) {
if (newPayees[i] == address(0)) {
revert Errors.CANNOT_SET_TO_ADDRESS_ZERO();
}
if (newShares[i] == 0) {
revert Errors.SHARE_CANNOT_BE_ZERO();
}
address payee = newPayees[i];
_payees.add(payee);
_shares[payee] = newShares[i];
_totalShares += newShares[i];
emit PayeeAdded(payee, newShares[i]);
}
}
function getKUMAAddressProvider() external view returns (IKUMAAddressProvider) {
return _KUMAAddressProvider;
}
function getRiskCategory() external view returns (bytes32) {
return _riskCategory;
}
/**
* @notice Internal helper function to release an available income to a all payees.
* @dev Uses totalShares to calculate correct share
* @param KIBToken Cached KIBToken for gas savings.
* @param availableIncome Available income to release to payees.
*/
function _release(IERC20 KIBToken, uint256 availableIncome) private {
uint256 totalShares = _totalShares;
for (uint256 i; i < _payees.length(); i++) {
address payee = _payees.at(i);
KIBToken.safeTransfer(payee, availableIncome * _shares[payee] / totalShares);
}
emit FeeReleased(availableIncome);
}
/**
* @notice Internal helper function to release an available income to a all payees if there is an availble income.
*/
function _releaseIfAvailableIncome() private {
IERC20 KIBToken = IERC20(_KUMAAddressProvider.getKIBToken(_riskCategory));
uint256 availableIncome = KIBToken.balanceOf(address(this));
if (availableIncome > 0) {
_release(KIBToken, availableIncome);
}
}
/**
* @return Array of current payees.
*/
function getPayees() external view returns (address[] memory) {
return _payees.values();
}
/**
* @return Total shares.
*/
function getTotalShares() external view returns (uint256) {
return _totalShares;
}
/**
* @return Share of specific payee.
*/
function getShare(address payee) external view returns (uint256) {
return _shares[payee];
}
function _authorizeUpgrade(address newImplementation) internal override onlyManager {}
}