Skip to content

Commit 17909eb

Browse files
authored
Add Manager permissions for protected modules and extensions (#71)
* Make fee extension setters use mutualUpgrade * Add BaseManagerV2 with module protection methods * Update tests for new BaseManager * StreamingFeeSplitExtension: add configurable operatorFeeRecipient * FeeSplitAdapter: add configurable operatorFeeRecipient * Send fees to fee extension * Update all specs to use BaseManagerV2 * Rename FeeSplitAdapter --> FeeSplitExtension * Add initialize method to StreamingFeeSplitExtension contract * Add initialize methods to FeeSplitExtension contract
1 parent 8dfa7be commit 17909eb

33 files changed

+4280
-1028
lines changed

contracts/adapters/FeeSplitAdapter.sol

Lines changed: 0 additions & 143 deletions
This file was deleted.
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
17+
pragma solidity 0.6.10;
18+
pragma experimental ABIEncoderV2;
19+
20+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
21+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
22+
23+
import { BaseExtension } from "../lib/BaseExtension.sol";
24+
import { IIssuanceModule } from "../interfaces/IIssuanceModule.sol";
25+
import { IBaseManager } from "../interfaces/IBaseManager.sol";
26+
import { ISetToken } from "../interfaces/ISetToken.sol";
27+
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
28+
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
29+
import { TimeLockUpgrade } from "../lib/TimeLockUpgrade.sol";
30+
import { MutualUpgrade } from "../lib/MutualUpgrade.sol";
31+
32+
33+
/**
34+
* @title FeeSplitExtension
35+
* @author Set Protocol
36+
*
37+
* Smart contract extension that allows for splitting and setting streaming and mint/redeem fees.
38+
*/
39+
contract FeeSplitExtension is BaseExtension, TimeLockUpgrade, MutualUpgrade {
40+
using Address for address;
41+
using PreciseUnitMath for uint256;
42+
using SafeMath for uint256;
43+
44+
/* ============ Events ============ */
45+
46+
event FeesDistributed(
47+
address indexed _operatorFeeRecipient,
48+
address indexed _methodologist,
49+
uint256 _operatorTake,
50+
uint256 _methodologistTake
51+
);
52+
53+
/* ============ State Variables ============ */
54+
55+
ISetToken public setToken;
56+
IStreamingFeeModule public streamingFeeModule;
57+
IIssuanceModule public issuanceModule;
58+
59+
// Percent of fees in precise units (10^16 = 1%) sent to operator, rest to methodologist
60+
uint256 public operatorFeeSplit;
61+
62+
// Address which receives operator's share of fees when they're distributed. (See IIP-72)
63+
address public operatorFeeRecipient;
64+
65+
/* ============ Constructor ============ */
66+
67+
constructor(
68+
IBaseManager _manager,
69+
IStreamingFeeModule _streamingFeeModule,
70+
IIssuanceModule _issuanceModule,
71+
uint256 _operatorFeeSplit,
72+
address _operatorFeeRecipient
73+
)
74+
public
75+
BaseExtension(_manager)
76+
{
77+
streamingFeeModule = _streamingFeeModule;
78+
issuanceModule = _issuanceModule;
79+
operatorFeeSplit = _operatorFeeSplit;
80+
operatorFeeRecipient = _operatorFeeRecipient;
81+
setToken = manager.setToken();
82+
}
83+
84+
/* ============ External Functions ============ */
85+
86+
/**
87+
* ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
88+
* operator and methodologist, and sends to operator fee recipient and methodologist respectively. NOTE: mint/redeem fees
89+
* will automatically be sent to this address so reading the balance of the SetToken in the contract after accrual is
90+
* sufficient for accounting for all collected fees.
91+
*/
92+
function accrueFeesAndDistribute() public {
93+
// Emits a FeeActualized event
94+
streamingFeeModule.accrueFee(setToken);
95+
96+
uint256 totalFees = setToken.balanceOf(address(this));
97+
98+
address methodologist = manager.methodologist();
99+
100+
uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit);
101+
uint256 methodologistTake = totalFees.sub(operatorTake);
102+
103+
if (operatorTake > 0) {
104+
setToken.transfer(operatorFeeRecipient, operatorTake);
105+
}
106+
107+
if (methodologistTake > 0) {
108+
setToken.transfer(methodologist, methodologistTake);
109+
}
110+
111+
emit FeesDistributed(operatorFeeRecipient, methodologist, operatorTake, methodologistTake);
112+
}
113+
114+
/**
115+
* MUTUAL UPGRADE: Initializes the issuance module. Operator and Methodologist must each call
116+
* this function to execute the update.
117+
*
118+
* This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
119+
* to configure the replacement streaming fee module's fee settings.
120+
*/
121+
function initializeIssuanceModule(
122+
ISetToken _setToken,
123+
uint256 _maxManagerFee,
124+
uint256 _managerIssueFee,
125+
uint256 _managerRedeemFee,
126+
address _feeRecipient,
127+
address _managerIssuanceHook
128+
)
129+
external
130+
mutualUpgrade(manager.operator(), manager.methodologist())
131+
{
132+
bytes memory callData = abi.encodeWithSelector(
133+
IIssuanceModule.initialize.selector,
134+
manager.setToken(),
135+
_maxManagerFee,
136+
_managerIssueFee,
137+
_managerRedeemFee,
138+
_feeRecipient,
139+
_managerIssuanceHook
140+
);
141+
142+
invokeManager(address(issuanceModule), callData);
143+
}
144+
145+
/**
146+
* MUTUAL UPGRADE: Initializes the issuance module. Operator and Methodologist must each call
147+
* this function to execute the update.
148+
*
149+
* This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
150+
* to configure the replacement streaming fee module's fee settings.
151+
*/
152+
function initializeStreamingFeeModule(IStreamingFeeModule.FeeState memory _settings)
153+
external
154+
mutualUpgrade(manager.operator(), manager.methodologist())
155+
{
156+
bytes memory callData = abi.encodeWithSelector(
157+
IStreamingFeeModule.initialize.selector,
158+
manager.setToken(),
159+
_settings
160+
);
161+
162+
invokeManager(address(streamingFeeModule), callData);
163+
}
164+
165+
/**
166+
* MUTUAL UPGRADE: Updates streaming fee on StreamingFeeModule. Operator and Methodologist must each call
167+
* this function to execute the update. Because the method is timelocked, each party must call it twice:
168+
* once to set the lock and once to execute.
169+
*
170+
* NOTE: This will accrue streaming fees though not send to operator fee recipient and methodologist.
171+
*/
172+
function updateStreamingFee(uint256 _newFee)
173+
external
174+
mutualUpgrade(manager.operator(), manager.methodologist())
175+
timeLockUpgrade
176+
{
177+
bytes memory callData = abi.encodeWithSignature("updateStreamingFee(address,uint256)", manager.setToken(), _newFee);
178+
invokeManager(address(streamingFeeModule), callData);
179+
}
180+
181+
/**
182+
* MUTUAL UPGRADE: Updates issue fee on IssuanceModule. Only is executed once time lock has passed.
183+
* Operator and Methodologist must each call this function to execute the update. Because the method
184+
* is timelocked, each party must call it twice: once to set the lock and once to execute.
185+
*/
186+
function updateIssueFee(uint256 _newFee)
187+
external
188+
mutualUpgrade(manager.operator(), manager.methodologist())
189+
timeLockUpgrade
190+
{
191+
bytes memory callData = abi.encodeWithSignature("updateIssueFee(address,uint256)", manager.setToken(), _newFee);
192+
invokeManager(address(issuanceModule), callData);
193+
}
194+
195+
/**
196+
* MUTUAL UPGRADE: Updates redeem fee on IssuanceModule. Only is executed once time lock has passed.
197+
* Operator and Methodologist must each call this function to execute the update. Because the method is
198+
* timelocked, each party must call it twice: once to set the lock and once to execute.
199+
*/
200+
function updateRedeemFee(uint256 _newFee)
201+
external
202+
mutualUpgrade(manager.operator(), manager.methodologist())
203+
timeLockUpgrade
204+
{
205+
bytes memory callData = abi.encodeWithSignature("updateRedeemFee(address,uint256)", manager.setToken(), _newFee);
206+
invokeManager(address(issuanceModule), callData);
207+
}
208+
209+
/**
210+
* MUTUAL UPGRADE: Updates fee recipient on both streaming fee and issuance modules.
211+
*/
212+
function updateFeeRecipient(address _newFeeRecipient)
213+
external
214+
mutualUpgrade(manager.operator(), manager.methodologist())
215+
{
216+
bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", manager.setToken(), _newFeeRecipient);
217+
invokeManager(address(streamingFeeModule), callData);
218+
invokeManager(address(issuanceModule), callData);
219+
}
220+
221+
/**
222+
* MUTUAL UPGRADE: Updates fee split between operator and methodologist. Split defined in precise units (1% = 10^16).
223+
*/
224+
function updateFeeSplit(uint256 _newFeeSplit)
225+
external
226+
mutualUpgrade(manager.operator(), manager.methodologist())
227+
{
228+
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
229+
accrueFeesAndDistribute();
230+
operatorFeeSplit = _newFeeSplit;
231+
}
232+
233+
/**
234+
* OPERATOR ONLY: Updates the address that receives the operator's share of the fees (see IIP-72)
235+
*/
236+
function updateOperatorFeeRecipient(address _newOperatorFeeRecipient)
237+
external
238+
onlyOperator
239+
{
240+
require(_newOperatorFeeRecipient != address(0), "Zero address not valid");
241+
operatorFeeRecipient = _newOperatorFeeRecipient;
242+
}
243+
}

0 commit comments

Comments
 (0)