1
+ /*
2
+ Copyright 2021 IndexCooperative
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+
16
+ SPDX-License-Identifier: Apache License, Version 2.0
17
+ */
18
+
19
+ pragma solidity 0.6.10 ;
20
+ pragma experimental ABIEncoderV2;
21
+
22
+ import { Address } from "@openzeppelin/contracts/utils/Address.sol " ;
23
+ import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol " ;
24
+
25
+ import { BaseExtension } from "../lib/BaseExtension.sol " ;
26
+ import { IBaseManager } from "../interfaces/IBaseManager.sol " ;
27
+ import { ISetToken } from "../interfaces/ISetToken.sol " ;
28
+ import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol " ;
29
+ import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol " ;
30
+ import { TimeLockUpgrade } from "../lib/TimeLockUpgrade.sol " ;
31
+ import { MutualUpgrade } from "../lib/MutualUpgrade.sol " ;
32
+
33
+
34
+ /**
35
+ * @title StreamingFeeSplitExtension
36
+ * @author Set Protocol
37
+ *
38
+ * Smart contract manager extension that allows for splitting and setting streaming fees. Fee splits are updated by operator.
39
+ * Any fee updates are timelocked.
40
+ */
41
+ contract StreamingFeeSplitExtension is BaseExtension , TimeLockUpgrade , MutualUpgrade {
42
+ using Address for address ;
43
+ using PreciseUnitMath for uint256 ;
44
+ using SafeMath for uint256 ;
45
+
46
+ /* ============ Events ============ */
47
+
48
+ event FeesDistributed (
49
+ address indexed _operatorFeeRecipient ,
50
+ address indexed _methodologist ,
51
+ uint256 _operatorTake ,
52
+ uint256 _methodologistTake
53
+ );
54
+
55
+ /* ============ State Variables ============ */
56
+
57
+ ISetToken public setToken;
58
+ IStreamingFeeModule public streamingFeeModule;
59
+
60
+ // Percent of fees in precise units (10^16 = 1%) sent to operator, rest to methodologist
61
+ uint256 public operatorFeeSplit;
62
+
63
+ // Address which receives operator's share of fees when they're distributed. (See IIP-72)
64
+ address public operatorFeeRecipient;
65
+
66
+ /* ============ Constructor ============ */
67
+
68
+ constructor (
69
+ IBaseManager _manager ,
70
+ IStreamingFeeModule _streamingFeeModule ,
71
+ uint256 _operatorFeeSplit ,
72
+ address _operatorFeeRecipient
73
+ )
74
+ public
75
+ BaseExtension (_manager)
76
+ {
77
+ streamingFeeModule = _streamingFeeModule;
78
+ operatorFeeSplit = _operatorFeeSplit;
79
+ operatorFeeRecipient = _operatorFeeRecipient;
80
+ setToken = manager.setToken ();
81
+ }
82
+
83
+ /* ============ External Functions ============ */
84
+
85
+ /**
86
+ * ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual,
87
+ * calculates fees for operator and methodologist, and sends to operatorFeeRecipient and methodologist
88
+ * respectively.
89
+ */
90
+ function accrueFeesAndDistribute () public {
91
+ // Emits a FeeActualized event
92
+ streamingFeeModule.accrueFee (setToken);
93
+
94
+ uint256 totalFees = setToken.balanceOf (address (this ));
95
+
96
+ address methodologist = manager.methodologist ();
97
+
98
+ uint256 operatorTake = totalFees.preciseMul (operatorFeeSplit);
99
+ uint256 methodologistTake = totalFees.sub (operatorTake);
100
+
101
+ if (operatorTake > 0 ) {
102
+ setToken.transfer (operatorFeeRecipient, operatorTake);
103
+ }
104
+
105
+ if (methodologistTake > 0 ) {
106
+ setToken.transfer (methodologist, methodologistTake);
107
+ }
108
+
109
+ emit FeesDistributed (operatorFeeRecipient, methodologist, operatorTake, methodologistTake);
110
+ }
111
+
112
+ /**
113
+ * MUTUAL UPGRADE: Initializes the streaming fee module. Operator and Methodologist must each call
114
+ * this function to execute the update.
115
+ *
116
+ * This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
117
+ * to configure the replacement streaming fee module's fee settings.
118
+ *
119
+ * @dev FeeState settings encode the following struct
120
+ * ```
121
+ * struct FeeState {
122
+ * address feeRecipient; // Address to accrue fees to
123
+ * uint256 maxStreamingFeePercentage; // Max streaming fee maanager commits to using (1% = 1e16, 100% = 1e18)
124
+ * uint256 streamingFeePercentage; // Percent of Set accruing to manager annually (1% = 1e16, 100% = 1e18)
125
+ * uint256 lastStreamingFeeTimestamp; // Timestamp last streaming fee was accrued
126
+ *}
127
+ *```
128
+ * @param _settings FeeModule.FeeState settings
129
+ */
130
+ function initializeModule (IStreamingFeeModule.FeeState memory _settings )
131
+ external
132
+ mutualUpgrade (manager.operator (), manager.methodologist ())
133
+ {
134
+ bytes memory callData = abi.encodeWithSelector (
135
+ IStreamingFeeModule.initialize.selector ,
136
+ manager.setToken (),
137
+ _settings
138
+ );
139
+
140
+ invokeManager (address (streamingFeeModule), callData);
141
+ }
142
+
143
+ /**
144
+ * MUTUAL UPGRADE: Updates streaming fee on StreamingFeeModule. Operator and Methodologist must
145
+ * each call this function to execute the update. Because the method is timelocked, each party
146
+ * must call it twice: once to set the lock and once to execute.
147
+ *
148
+ * Method is timelocked to protect token owners from sudden changes in fee structure which
149
+ * they would rather not bear. The delay gives them a chance to exit their positions without penalty.
150
+ *
151
+ * NOTE: This will accrue streaming fees though not send to operator fee recipient and methodologist.
152
+ *
153
+ * @param _newFee Percent of Set accruing to fee extension annually (1% = 1e16, 100% = 1e18)
154
+ */
155
+ function updateStreamingFee (uint256 _newFee )
156
+ external
157
+ mutualUpgrade (manager.operator (), manager.methodologist ())
158
+ timeLockUpgrade
159
+ {
160
+ bytes memory callData = abi.encodeWithSelector (
161
+ IStreamingFeeModule.updateStreamingFee.selector ,
162
+ manager.setToken (),
163
+ _newFee
164
+ );
165
+
166
+ invokeManager (address (streamingFeeModule), callData);
167
+ }
168
+
169
+ /**
170
+ * MUTUAL UPGRADE: Updates fee recipient on streaming fee module.
171
+ *
172
+ * @param _newFeeRecipient Address of new fee recipient. This should be the address of the fee extension itself.
173
+ */
174
+ function updateFeeRecipient (address _newFeeRecipient )
175
+ external
176
+ mutualUpgrade (manager.operator (), manager.methodologist ())
177
+ {
178
+ bytes memory callData = abi.encodeWithSelector (
179
+ IStreamingFeeModule.updateFeeRecipient.selector ,
180
+ manager.setToken (),
181
+ _newFeeRecipient
182
+ );
183
+
184
+ invokeManager (address (streamingFeeModule), callData);
185
+ }
186
+
187
+ /**
188
+ * MUTUAL UPGRADE: Updates fee split between operator and methodologist. Split defined in precise units (1% = 10^16).
189
+ * Fees will be accrued and distributed before the new split goes into effect.
190
+ *
191
+ * @param _newFeeSplit Percent of fees in precise units (10^16 = 1%) sent to operator, (rest go to the methodologist).
192
+ */
193
+ function updateFeeSplit (uint256 _newFeeSplit )
194
+ external
195
+ mutualUpgrade (manager.operator (), manager.methodologist ())
196
+ {
197
+ require (_newFeeSplit <= PreciseUnitMath.preciseUnit (), "Fee must be less than 100% " );
198
+ accrueFeesAndDistribute ();
199
+ operatorFeeSplit = _newFeeSplit;
200
+ }
201
+
202
+ /**
203
+ * OPERATOR ONLY: Updates the address that receives the operator's share of the fees (see IIP-72)
204
+ *
205
+ * @param _newOperatorFeeRecipient Address to send operator's fees to.
206
+ */
207
+ function updateOperatorFeeRecipient (address _newOperatorFeeRecipient )
208
+ external
209
+ onlyOperator
210
+ {
211
+ require (_newOperatorFeeRecipient != address (0 ), "Zero address not valid " );
212
+ operatorFeeRecipient = _newOperatorFeeRecipient;
213
+ }
214
+ }
0 commit comments