Skip to content

Commit

Permalink
Add Manager permissions for protected modules and extensions (#71)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
cgewecke authored Aug 19, 2021
1 parent 8dfa7be commit 17909eb
Show file tree
Hide file tree
Showing 33 changed files with 4,280 additions and 1,028 deletions.
143 changes: 0 additions & 143 deletions contracts/adapters/FeeSplitAdapter.sol

This file was deleted.

243 changes: 243 additions & 0 deletions contracts/adapters/FeeSplitExtension.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
Copyright 2021 IndexCooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;

import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";

import { BaseExtension } from "../lib/BaseExtension.sol";
import { IIssuanceModule } from "../interfaces/IIssuanceModule.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { TimeLockUpgrade } from "../lib/TimeLockUpgrade.sol";
import { MutualUpgrade } from "../lib/MutualUpgrade.sol";


/**
* @title FeeSplitExtension
* @author Set Protocol
*
* Smart contract extension that allows for splitting and setting streaming and mint/redeem fees.
*/
contract FeeSplitExtension is BaseExtension, TimeLockUpgrade, MutualUpgrade {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;

/* ============ Events ============ */

event FeesDistributed(
address indexed _operatorFeeRecipient,
address indexed _methodologist,
uint256 _operatorTake,
uint256 _methodologistTake
);

/* ============ State Variables ============ */

ISetToken public setToken;
IStreamingFeeModule public streamingFeeModule;
IIssuanceModule public issuanceModule;

// Percent of fees in precise units (10^16 = 1%) sent to operator, rest to methodologist
uint256 public operatorFeeSplit;

// Address which receives operator's share of fees when they're distributed. (See IIP-72)
address public operatorFeeRecipient;

/* ============ Constructor ============ */

constructor(
IBaseManager _manager,
IStreamingFeeModule _streamingFeeModule,
IIssuanceModule _issuanceModule,
uint256 _operatorFeeSplit,
address _operatorFeeRecipient
)
public
BaseExtension(_manager)
{
streamingFeeModule = _streamingFeeModule;
issuanceModule = _issuanceModule;
operatorFeeSplit = _operatorFeeSplit;
operatorFeeRecipient = _operatorFeeRecipient;
setToken = manager.setToken();
}

/* ============ External Functions ============ */

/**
* ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
* operator and methodologist, and sends to operator fee recipient and methodologist respectively. NOTE: mint/redeem fees
* will automatically be sent to this address so reading the balance of the SetToken in the contract after accrual is
* sufficient for accounting for all collected fees.
*/
function accrueFeesAndDistribute() public {
// Emits a FeeActualized event
streamingFeeModule.accrueFee(setToken);

uint256 totalFees = setToken.balanceOf(address(this));

address methodologist = manager.methodologist();

uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit);
uint256 methodologistTake = totalFees.sub(operatorTake);

if (operatorTake > 0) {
setToken.transfer(operatorFeeRecipient, operatorTake);
}

if (methodologistTake > 0) {
setToken.transfer(methodologist, methodologistTake);
}

emit FeesDistributed(operatorFeeRecipient, methodologist, operatorTake, methodologistTake);
}

/**
* MUTUAL UPGRADE: Initializes the issuance module. Operator and Methodologist must each call
* this function to execute the update.
*
* This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
* to configure the replacement streaming fee module's fee settings.
*/
function initializeIssuanceModule(
ISetToken _setToken,
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
address _managerIssuanceHook
)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSelector(
IIssuanceModule.initialize.selector,
manager.setToken(),
_maxManagerFee,
_managerIssueFee,
_managerRedeemFee,
_feeRecipient,
_managerIssuanceHook
);

invokeManager(address(issuanceModule), callData);
}

/**
* MUTUAL UPGRADE: Initializes the issuance module. Operator and Methodologist must each call
* this function to execute the update.
*
* This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
* to configure the replacement streaming fee module's fee settings.
*/
function initializeStreamingFeeModule(IStreamingFeeModule.FeeState memory _settings)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSelector(
IStreamingFeeModule.initialize.selector,
manager.setToken(),
_settings
);

invokeManager(address(streamingFeeModule), callData);
}

/**
* MUTUAL UPGRADE: Updates streaming fee on StreamingFeeModule. Operator and Methodologist must each call
* this function to execute the update. Because the method is timelocked, each party must call it twice:
* once to set the lock and once to execute.
*
* NOTE: This will accrue streaming fees though not send to operator fee recipient and methodologist.
*/
function updateStreamingFee(uint256 _newFee)
external
mutualUpgrade(manager.operator(), manager.methodologist())
timeLockUpgrade
{
bytes memory callData = abi.encodeWithSignature("updateStreamingFee(address,uint256)", manager.setToken(), _newFee);
invokeManager(address(streamingFeeModule), callData);
}

/**
* MUTUAL UPGRADE: Updates issue fee on IssuanceModule. Only is executed once time lock has passed.
* Operator and Methodologist must each call this function to execute the update. Because the method
* is timelocked, each party must call it twice: once to set the lock and once to execute.
*/
function updateIssueFee(uint256 _newFee)
external
mutualUpgrade(manager.operator(), manager.methodologist())
timeLockUpgrade
{
bytes memory callData = abi.encodeWithSignature("updateIssueFee(address,uint256)", manager.setToken(), _newFee);
invokeManager(address(issuanceModule), callData);
}

/**
* MUTUAL UPGRADE: Updates redeem fee on IssuanceModule. Only is executed once time lock has passed.
* Operator and Methodologist must each call this function to execute the update. Because the method is
* timelocked, each party must call it twice: once to set the lock and once to execute.
*/
function updateRedeemFee(uint256 _newFee)
external
mutualUpgrade(manager.operator(), manager.methodologist())
timeLockUpgrade
{
bytes memory callData = abi.encodeWithSignature("updateRedeemFee(address,uint256)", manager.setToken(), _newFee);
invokeManager(address(issuanceModule), callData);
}

/**
* MUTUAL UPGRADE: Updates fee recipient on both streaming fee and issuance modules.
*/
function updateFeeRecipient(address _newFeeRecipient)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", manager.setToken(), _newFeeRecipient);
invokeManager(address(streamingFeeModule), callData);
invokeManager(address(issuanceModule), callData);
}

/**
* MUTUAL UPGRADE: Updates fee split between operator and methodologist. Split defined in precise units (1% = 10^16).
*/
function updateFeeSplit(uint256 _newFeeSplit)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
accrueFeesAndDistribute();
operatorFeeSplit = _newFeeSplit;
}

/**
* OPERATOR ONLY: Updates the address that receives the operator's share of the fees (see IIP-72)
*/
function updateOperatorFeeRecipient(address _newOperatorFeeRecipient)
external
onlyOperator
{
require(_newOperatorFeeRecipient != address(0), "Zero address not valid");
operatorFeeRecipient = _newOperatorFeeRecipient;
}
}
Loading

0 comments on commit 17909eb

Please sign in to comment.