Contract Name:
PerformanceFee
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a % b;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @title SignedSafeMath
* @dev Signed math operations with safety checks that revert on error.
*/
library SignedSafeMath {
int256 constant private _INT256_MIN = -2**255;
/**
* @dev Returns the multiplication of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(int256 a, int256 b) internal pure returns (int256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
require(!(a == -1 && b == _INT256_MIN), "SignedSafeMath: multiplication overflow");
int256 c = a * b;
require(c / a == b, "SignedSafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two signed integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(int256 a, int256 b) internal pure returns (int256) {
require(b != 0, "SignedSafeMath: division by zero");
require(!(b == -1 && a == _INT256_MIN), "SignedSafeMath: division overflow");
int256 c = a / b;
return c;
}
/**
* @dev Returns the subtraction of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(int256 a, int256 b) internal pure returns (int256) {
int256 c = a - b;
require((b >= 0 && c <= a) || (b < 0 && c > a), "SignedSafeMath: subtraction overflow");
return c;
}
/**
* @dev Returns the addition of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(int256 a, int256 b) internal pure returns (int256) {
int256 c = a + b;
require((b >= 0 && c >= a) || (b < 0 && c < a), "SignedSafeMath: addition overflow");
return c;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../../utils/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with
* a default value of 18.
*
* To select a different value for {decimals}, use {_setupDecimals}.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name_, string memory symbol_) public {
_name = name_;
_symbol = symbol_;
_decimals = 18;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return _decimals;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* Requirements:
*
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* WARNING: This function should only be called from the constructor. Most
* applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does.
*/
function _setupDecimals(uint8 decimals_) internal virtual {
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping (bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) { // Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
// When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs
// so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
bytes32 lastvalue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Update the index for the moved value
set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
require(set._values.length > index, "EnumerableSet: index out of bounds");
return set._values[index];
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IDispatcher Interface
/// @author Enzyme Council <[email protected]>
interface IDispatcher {
function cancelMigration(address _vaultProxy, bool _bypassFailure) external;
function claimOwnership() external;
function deployVaultProxy(
address _vaultLib,
address _owner,
address _vaultAccessor,
string calldata _fundName
) external returns (address vaultProxy_);
function executeMigration(address _vaultProxy, bool _bypassFailure) external;
function getCurrentFundDeployer() external view returns (address currentFundDeployer_);
function getFundDeployerForVaultProxy(address _vaultProxy)
external
view
returns (address fundDeployer_);
function getMigrationRequestDetailsForVaultProxy(address _vaultProxy)
external
view
returns (
address nextFundDeployer_,
address nextVaultAccessor_,
address nextVaultLib_,
uint256 executableTimestamp_
);
function getMigrationTimelock() external view returns (uint256 migrationTimelock_);
function getNominatedOwner() external view returns (address nominatedOwner_);
function getOwner() external view returns (address owner_);
function getSharesTokenSymbol() external view returns (string memory sharesTokenSymbol_);
function getTimelockRemainingForMigrationRequest(address _vaultProxy)
external
view
returns (uint256 secondsRemaining_);
function hasExecutableMigrationRequest(address _vaultProxy)
external
view
returns (bool hasExecutableRequest_);
function hasMigrationRequest(address _vaultProxy)
external
view
returns (bool hasMigrationRequest_);
function removeNominatedOwner() external;
function setCurrentFundDeployer(address _nextFundDeployer) external;
function setMigrationTimelock(uint256 _nextTimelock) external;
function setNominatedOwner(address _nextNominatedOwner) external;
function setSharesTokenSymbol(string calldata _nextSymbol) external;
function signalMigration(
address _vaultProxy,
address _nextVaultAccessor,
address _nextVaultLib,
bool _bypassFailure
) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IMigratableVault Interface
/// @author Enzyme Council <[email protected]>
/// @dev DO NOT EDIT CONTRACT
interface IMigratableVault {
function canMigrate(address _who) external view returns (bool canMigrate_);
function init(
address _owner,
address _accessor,
string calldata _fundName
) external;
function setAccessor(address _nextAccessor) external;
function setVaultLib(address _nextVaultLib) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IFundDeployer Interface
/// @author Enzyme Council <[email protected]>
interface IFundDeployer {
enum ReleaseStatus {PreLaunch, Live, Paused}
function getOwner() external view returns (address);
function getReleaseStatus() external view returns (ReleaseStatus);
function isRegisteredVaultCall(address, bytes4) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "../../../../persistent/dispatcher/IDispatcher.sol";
import "../../../extensions/IExtension.sol";
import "../../../extensions/fee-manager/IFeeManager.sol";
import "../../../extensions/policy-manager/IPolicyManager.sol";
import "../../../infrastructure/price-feeds/primitives/IPrimitivePriceFeed.sol";
import "../../../infrastructure/value-interpreter/IValueInterpreter.sol";
import "../../../utils/AddressArrayLib.sol";
import "../../../utils/AssetFinalityResolver.sol";
import "../../fund-deployer/IFundDeployer.sol";
import "../vault/IVault.sol";
import "./IComptroller.sol";
/// @title ComptrollerLib Contract
/// @author Enzyme Council <[email protected]>
/// @notice The core logic library shared by all funds
contract ComptrollerLib is IComptroller, AssetFinalityResolver {
using AddressArrayLib for address[];
using SafeMath for uint256;
using SafeERC20 for ERC20;
event MigratedSharesDuePaid(uint256 sharesDue);
event OverridePauseSet(bool indexed overridePause);
event PreRedeemSharesHookFailed(
bytes failureReturnData,
address redeemer,
uint256 sharesQuantity
);
event SharesBought(
address indexed caller,
address indexed buyer,
uint256 investmentAmount,
uint256 sharesIssued,
uint256 sharesReceived
);
event SharesRedeemed(
address indexed redeemer,
uint256 sharesQuantity,
address[] receivedAssets,
uint256[] receivedAssetQuantities
);
event VaultProxySet(address vaultProxy);
// Constants and immutables - shared by all proxies
uint256 private constant SHARES_UNIT = 10**18;
address private immutable DISPATCHER;
address private immutable FUND_DEPLOYER;
address private immutable FEE_MANAGER;
address private immutable INTEGRATION_MANAGER;
address private immutable PRIMITIVE_PRICE_FEED;
address private immutable POLICY_MANAGER;
address private immutable VALUE_INTERPRETER;
// Pseudo-constants (can only be set once)
address internal denominationAsset;
address internal vaultProxy;
// True only for the one non-proxy
bool internal isLib;
// Storage
// Allows a fund owner to override a release-level pause
bool internal overridePause;
// A reverse-mutex, granting atomic permission for particular contracts to make vault calls
bool internal permissionedVaultActionAllowed;
// A mutex to protect against reentrancy
bool internal reentranceLocked;
// A timelock between any "shares actions" (i.e., buy and redeem shares), per-account
uint256 internal sharesActionTimelock;
mapping(address => uint256) internal acctToLastSharesAction;
///////////////
// MODIFIERS //
///////////////
modifier allowsPermissionedVaultAction {
__assertPermissionedVaultActionNotAllowed();
permissionedVaultActionAllowed = true;
_;
permissionedVaultActionAllowed = false;
}
modifier locksReentrance() {
__assertNotReentranceLocked();
reentranceLocked = true;
_;
reentranceLocked = false;
}
modifier onlyActive() {
__assertIsActive(vaultProxy);
_;
}
modifier onlyNotPaused() {
__assertNotPaused();
_;
}
modifier onlyFundDeployer() {
__assertIsFundDeployer(msg.sender);
_;
}
modifier onlyOwner() {
__assertIsOwner(msg.sender);
_;
}
modifier timelockedSharesAction(address _account) {
__assertSharesActionNotTimelocked(_account);
_;
acctToLastSharesAction[_account] = block.timestamp;
}
// ASSERTION HELPERS
// Modifiers are inefficient in terms of contract size,
// so we use helper functions to prevent repetitive inlining of expensive string values.
/// @dev Since vaultProxy is set during activate(),
/// we can check that var rather than storing additional state
function __assertIsActive(address _vaultProxy) private pure {
require(_vaultProxy != address(0), "Fund not active");
}
function __assertIsFundDeployer(address _who) private view {
require(_who == FUND_DEPLOYER, "Only FundDeployer callable");
}
function __assertIsOwner(address _who) private view {
require(_who == IVault(vaultProxy).getOwner(), "Only fund owner callable");
}
function __assertLowLevelCall(bool _success, bytes memory _returnData) private pure {
require(_success, string(_returnData));
}
function __assertNotPaused() private view {
require(!__fundIsPaused(), "Fund is paused");
}
function __assertNotReentranceLocked() private view {
require(!reentranceLocked, "Re-entrance");
}
function __assertPermissionedVaultActionNotAllowed() private view {
require(!permissionedVaultActionAllowed, "Vault action re-entrance");
}
function __assertSharesActionNotTimelocked(address _account) private view {
require(
block.timestamp.sub(acctToLastSharesAction[_account]) >= sharesActionTimelock,
"Shares action timelocked"
);
}
constructor(
address _dispatcher,
address _fundDeployer,
address _valueInterpreter,
address _feeManager,
address _integrationManager,
address _policyManager,
address _primitivePriceFeed,
address _synthetixPriceFeed,
address _synthetixAddressResolver
) public AssetFinalityResolver(_synthetixPriceFeed, _synthetixAddressResolver) {
DISPATCHER = _dispatcher;
FEE_MANAGER = _feeManager;
FUND_DEPLOYER = _fundDeployer;
INTEGRATION_MANAGER = _integrationManager;
PRIMITIVE_PRICE_FEED = _primitivePriceFeed;
POLICY_MANAGER = _policyManager;
VALUE_INTERPRETER = _valueInterpreter;
isLib = true;
}
/////////////
// GENERAL //
/////////////
/// @notice Calls a specified action on an Extension
/// @param _extension The Extension contract to call (e.g., FeeManager)
/// @param _actionId An ID representing the action to take on the extension (see extension)
/// @param _callArgs The encoded data for the call
/// @dev Used to route arbitrary calls, so that msg.sender is the ComptrollerProxy
/// (for access control). Uses a mutex of sorts that allows "permissioned vault actions"
/// during calls originating from this function.
function callOnExtension(
address _extension,
uint256 _actionId,
bytes calldata _callArgs
) external override onlyNotPaused onlyActive locksReentrance allowsPermissionedVaultAction {
require(
_extension == FEE_MANAGER || _extension == INTEGRATION_MANAGER,
"callOnExtension: _extension invalid"
);
IExtension(_extension).receiveCallFromComptroller(msg.sender, _actionId, _callArgs);
}
/// @notice Sets or unsets an override on a release-wide pause
/// @param _nextOverridePause True if the pause should be overrode
function setOverridePause(bool _nextOverridePause) external onlyOwner {
require(_nextOverridePause != overridePause, "setOverridePause: Value already set");
overridePause = _nextOverridePause;
emit OverridePauseSet(_nextOverridePause);
}
/// @notice Makes an arbitrary call with the VaultProxy contract as the sender
/// @param _contract The contract to call
/// @param _selector The selector to call
/// @param _encodedArgs The encoded arguments for the call
function vaultCallOnContract(
address _contract,
bytes4 _selector,
bytes calldata _encodedArgs
) external onlyNotPaused onlyActive onlyOwner {
require(
IFundDeployer(FUND_DEPLOYER).isRegisteredVaultCall(_contract, _selector),
"vaultCallOnContract: Unregistered"
);
IVault(vaultProxy).callOnContract(_contract, abi.encodePacked(_selector, _encodedArgs));
}
/// @dev Helper to check whether the release is paused, and that there is no local override
function __fundIsPaused() private view returns (bool) {
return
IFundDeployer(FUND_DEPLOYER).getReleaseStatus() ==
IFundDeployer.ReleaseStatus.Paused &&
!overridePause;
}
////////////////////////////////
// PERMISSIONED VAULT ACTIONS //
////////////////////////////////
/// @notice Makes a permissioned, state-changing call on the VaultProxy contract
/// @param _action The enum representing the VaultAction to perform on the VaultProxy
/// @param _actionData The call data for the action to perform
function permissionedVaultAction(VaultAction _action, bytes calldata _actionData)
external
override
onlyNotPaused
onlyActive
{
__assertPermissionedVaultAction(msg.sender, _action);
if (_action == VaultAction.AddTrackedAsset) {
__vaultActionAddTrackedAsset(_actionData);
} else if (_action == VaultAction.ApproveAssetSpender) {
__vaultActionApproveAssetSpender(_actionData);
} else if (_action == VaultAction.BurnShares) {
__vaultActionBurnShares(_actionData);
} else if (_action == VaultAction.MintShares) {
__vaultActionMintShares(_actionData);
} else if (_action == VaultAction.RemoveTrackedAsset) {
__vaultActionRemoveTrackedAsset(_actionData);
} else if (_action == VaultAction.TransferShares) {
__vaultActionTransferShares(_actionData);
} else if (_action == VaultAction.WithdrawAssetTo) {
__vaultActionWithdrawAssetTo(_actionData);
}
}
/// @dev Helper to assert that a caller is allowed to perform a particular VaultAction
function __assertPermissionedVaultAction(address _caller, VaultAction _action) private view {
require(
permissionedVaultActionAllowed,
"__assertPermissionedVaultAction: No action allowed"
);
if (_caller == INTEGRATION_MANAGER) {
require(
_action == VaultAction.ApproveAssetSpender ||
_action == VaultAction.AddTrackedAsset ||
_action == VaultAction.RemoveTrackedAsset ||
_action == VaultAction.WithdrawAssetTo,
"__assertPermissionedVaultAction: Not valid for IntegrationManager"
);
} else if (_caller == FEE_MANAGER) {
require(
_action == VaultAction.BurnShares ||
_action == VaultAction.MintShares ||
_action == VaultAction.TransferShares,
"__assertPermissionedVaultAction: Not valid for FeeManager"
);
} else {
revert("__assertPermissionedVaultAction: Not a valid actor");
}
}
/// @dev Helper to add a tracked asset to the fund
function __vaultActionAddTrackedAsset(bytes memory _actionData) private {
address asset = abi.decode(_actionData, (address));
IVault(vaultProxy).addTrackedAsset(asset);
}
/// @dev Helper to grant a spender an allowance for a fund's asset
function __vaultActionApproveAssetSpender(bytes memory _actionData) private {
(address asset, address target, uint256 amount) = abi.decode(
_actionData,
(address, address, uint256)
);
IVault(vaultProxy).approveAssetSpender(asset, target, amount);
}
/// @dev Helper to burn fund shares for a particular account
function __vaultActionBurnShares(bytes memory _actionData) private {
(address target, uint256 amount) = abi.decode(_actionData, (address, uint256));
IVault(vaultProxy).burnShares(target, amount);
}
/// @dev Helper to mint fund shares to a particular account
function __vaultActionMintShares(bytes memory _actionData) private {
(address target, uint256 amount) = abi.decode(_actionData, (address, uint256));
IVault(vaultProxy).mintShares(target, amount);
}
/// @dev Helper to remove a tracked asset from the fund
function __vaultActionRemoveTrackedAsset(bytes memory _actionData) private {
address asset = abi.decode(_actionData, (address));
// Allowing this to fail silently makes it cheaper and simpler
// for Extensions to not query for the denomination asset
if (asset != denominationAsset) {
IVault(vaultProxy).removeTrackedAsset(asset);
}
}
/// @dev Helper to transfer fund shares from one account to another
function __vaultActionTransferShares(bytes memory _actionData) private {
(address from, address to, uint256 amount) = abi.decode(
_actionData,
(address, address, uint256)
);
IVault(vaultProxy).transferShares(from, to, amount);
}
/// @dev Helper to withdraw an asset from the VaultProxy to a given account
function __vaultActionWithdrawAssetTo(bytes memory _actionData) private {
(address asset, address target, uint256 amount) = abi.decode(
_actionData,
(address, address, uint256)
);
IVault(vaultProxy).withdrawAssetTo(asset, target, amount);
}
///////////////
// LIFECYCLE //
///////////////
/// @notice Initializes a fund with its core config
/// @param _denominationAsset The asset in which the fund's value should be denominated
/// @param _sharesActionTimelock The minimum number of seconds between any two "shares actions"
/// (buying or selling shares) by the same user
/// @dev Pseudo-constructor per proxy.
/// No need to assert access because this is called atomically on deployment,
/// and once it's called, it cannot be called again.
function init(address _denominationAsset, uint256 _sharesActionTimelock) external override {
require(denominationAsset == address(0), "init: Already initialized");
require(
IPrimitivePriceFeed(PRIMITIVE_PRICE_FEED).isSupportedAsset(_denominationAsset),
"init: Bad denomination asset"
);
denominationAsset = _denominationAsset;
sharesActionTimelock = _sharesActionTimelock;
}
/// @notice Configure the extensions of a fund
/// @param _feeManagerConfigData Encoded config for fees to enable
/// @param _policyManagerConfigData Encoded config for policies to enable
/// @dev No need to assert anything beyond FundDeployer access.
/// Called atomically with init(), but after ComptrollerLib has been deployed,
/// giving access to its state and interface
function configureExtensions(
bytes calldata _feeManagerConfigData,
bytes calldata _policyManagerConfigData
) external override onlyFundDeployer {
if (_feeManagerConfigData.length > 0) {
IExtension(FEE_MANAGER).setConfigForFund(_feeManagerConfigData);
}
if (_policyManagerConfigData.length > 0) {
IExtension(POLICY_MANAGER).setConfigForFund(_policyManagerConfigData);
}
}
/// @notice Activates the fund by attaching a VaultProxy and activating all Extensions
/// @param _vaultProxy The VaultProxy to attach to the fund
/// @param _isMigration True if a migrated fund is being activated
/// @dev No need to assert anything beyond FundDeployer access.
function activate(address _vaultProxy, bool _isMigration) external override onlyFundDeployer {
vaultProxy = _vaultProxy;
emit VaultProxySet(_vaultProxy);
if (_isMigration) {
// Distribute any shares in the VaultProxy to the fund owner.
// This is a mechanism to ensure that even in the edge case of a fund being unable
// to payout fee shares owed during migration, these shares are not lost.
uint256 sharesDue = ERC20(_vaultProxy).balanceOf(_vaultProxy);
if (sharesDue > 0) {
IVault(_vaultProxy).transferShares(
_vaultProxy,
IVault(_vaultProxy).getOwner(),
sharesDue
);
emit MigratedSharesDuePaid(sharesDue);
}
}
// Note: a future release could consider forcing the adding of a tracked asset here,
// just in case a fund is migrating from an old configuration where they are not able
// to remove an asset to get under the tracked assets limit
IVault(_vaultProxy).addTrackedAsset(denominationAsset);
// Activate extensions
IExtension(FEE_MANAGER).activateForFund(_isMigration);
IExtension(INTEGRATION_MANAGER).activateForFund(_isMigration);
IExtension(POLICY_MANAGER).activateForFund(_isMigration);
}
/// @notice Remove the config for a fund
/// @dev No need to assert anything beyond FundDeployer access.
/// Calling onlyNotPaused here rather than in the FundDeployer allows
/// the owner to potentially override the pause and rescue unpaid fees.
function destruct()
external
override
onlyFundDeployer
onlyNotPaused
allowsPermissionedVaultAction
{
// Failsafe to protect the libs against selfdestruct
require(!isLib, "destruct: Only delegate callable");
// Deactivate the extensions
IExtension(FEE_MANAGER).deactivateForFund();
IExtension(INTEGRATION_MANAGER).deactivateForFund();
IExtension(POLICY_MANAGER).deactivateForFund();
// Delete storage of ComptrollerProxy
// There should never be ETH in the ComptrollerLib, so no need to waste gas
// to get the fund owner
selfdestruct(address(0));
}
////////////////
// ACCOUNTING //
////////////////
/// @notice Calculates the gross asset value (GAV) of the fund
/// @param _requireFinality True if all assets must have exact final balances settled
/// @return gav_ The fund GAV
/// @return isValid_ True if the conversion rates used to derive the GAV are all valid
function calcGav(bool _requireFinality) public override returns (uint256 gav_, bool isValid_) {
address vaultProxyAddress = vaultProxy;
address[] memory assets = IVault(vaultProxyAddress).getTrackedAssets();
if (assets.length == 0) {
return (0, true);
}
uint256[] memory balances = new uint256[](assets.length);
for (uint256 i; i < assets.length; i++) {
balances[i] = __finalizeIfSynthAndGetAssetBalance(
vaultProxyAddress,
assets[i],
_requireFinality
);
}
(gav_, isValid_) = IValueInterpreter(VALUE_INTERPRETER).calcCanonicalAssetsTotalValue(
assets,
balances,
denominationAsset
);
return (gav_, isValid_);
}
/// @notice Calculates the gross value of 1 unit of shares in the fund's denomination asset
/// @param _requireFinality True if all assets must have exact final balances settled
/// @return grossShareValue_ The amount of the denomination asset per share
/// @return isValid_ True if the conversion rates to derive the value are all valid
/// @dev Does not account for any fees outstanding.
function calcGrossShareValue(bool _requireFinality)
external
override
returns (uint256 grossShareValue_, bool isValid_)
{
uint256 gav;
(gav, isValid_) = calcGav(_requireFinality);
grossShareValue_ = __calcGrossShareValue(
gav,
ERC20(vaultProxy).totalSupply(),
10**uint256(ERC20(denominationAsset).decimals())
);
return (grossShareValue_, isValid_);
}
/// @dev Helper for calculating the gross share value
function __calcGrossShareValue(
uint256 _gav,
uint256 _sharesSupply,
uint256 _denominationAssetUnit
) private pure returns (uint256 grossShareValue_) {
if (_sharesSupply == 0) {
return _denominationAssetUnit;
}
return _gav.mul(SHARES_UNIT).div(_sharesSupply);
}
///////////////////
// PARTICIPATION //
///////////////////
// BUY SHARES
/// @notice Buys shares in the fund for multiple sets of criteria
/// @param _buyers The accounts for which to buy shares
/// @param _investmentAmounts The amounts of the fund's denomination asset
/// with which to buy shares for the corresponding _buyers
/// @param _minSharesQuantities The minimum quantities of shares to buy
/// with the corresponding _investmentAmounts
/// @return sharesReceivedAmounts_ The actual amounts of shares received
/// by the corresponding _buyers
/// @dev Param arrays have indexes corresponding to individual __buyShares() orders.
function buyShares(
address[] calldata _buyers,
uint256[] calldata _investmentAmounts,
uint256[] calldata _minSharesQuantities
)
external
onlyNotPaused
locksReentrance
allowsPermissionedVaultAction
returns (uint256[] memory sharesReceivedAmounts_)
{
require(_buyers.length > 0, "buyShares: Empty _buyers");
require(
_buyers.length == _investmentAmounts.length &&
_buyers.length == _minSharesQuantities.length,
"buyShares: Unequal arrays"
);
address vaultProxyCopy = vaultProxy;
__assertIsActive(vaultProxyCopy);
require(
!IDispatcher(DISPATCHER).hasMigrationRequest(vaultProxyCopy),
"buyShares: Pending migration"
);
(uint256 gav, bool gavIsValid) = calcGav(true);
require(gavIsValid, "buyShares: Invalid GAV");
__buySharesSetupHook(msg.sender, _investmentAmounts, gav);
address denominationAssetCopy = denominationAsset;
uint256 sharePrice = __calcGrossShareValue(
gav,
ERC20(vaultProxyCopy).totalSupply(),
10**uint256(ERC20(denominationAssetCopy).decimals())
);
sharesReceivedAmounts_ = new uint256[](_buyers.length);
for (uint256 i; i < _buyers.length; i++) {
sharesReceivedAmounts_[i] = __buyShares(
_buyers[i],
_investmentAmounts[i],
_minSharesQuantities[i],
vaultProxyCopy,
sharePrice,
gav,
denominationAssetCopy
);
gav = gav.add(_investmentAmounts[i]);
}
__buySharesCompletedHook(msg.sender, sharesReceivedAmounts_, gav);
return sharesReceivedAmounts_;
}
/// @dev Helper to buy shares
function __buyShares(
address _buyer,
uint256 _investmentAmount,
uint256 _minSharesQuantity,
address _vaultProxy,
uint256 _sharePrice,
uint256 _preBuySharesGav,
address _denominationAsset
) private timelockedSharesAction(_buyer) returns (uint256 sharesReceived_) {
require(_investmentAmount > 0, "__buyShares: Empty _investmentAmount");
// Gives Extensions a chance to run logic prior to the minting of bought shares
__preBuySharesHook(_buyer, _investmentAmount, _minSharesQuantity, _preBuySharesGav);
// Calculate the amount of shares to issue with the investment amount
uint256 sharesIssued = _investmentAmount.mul(SHARES_UNIT).div(_sharePrice);
// Mint shares to the buyer
uint256 prevBuyerShares = ERC20(_vaultProxy).balanceOf(_buyer);
IVault(_vaultProxy).mintShares(_buyer, sharesIssued);
// Transfer the investment asset to the fund.
// Does not follow the checks-effects-interactions pattern, but it is preferred
// to have the final state of the VaultProxy prior to running __postBuySharesHook().
ERC20(_denominationAsset).safeTransferFrom(msg.sender, _vaultProxy, _investmentAmount);
// Gives Extensions a chance to run logic after shares are issued
__postBuySharesHook(_buyer, _investmentAmount, sharesIssued, _preBuySharesGav);
// The number of actual shares received may differ from shares issued due to
// how the PostBuyShares hooks are invoked by Extensions (i.e., fees)
sharesReceived_ = ERC20(_vaultProxy).balanceOf(_buyer).sub(prevBuyerShares);
require(
sharesReceived_ >= _minSharesQuantity,
"__buyShares: Shares received < _minSharesQuantity"
);
emit SharesBought(msg.sender, _buyer, _investmentAmount, sharesIssued, sharesReceived_);
return sharesReceived_;
}
/// @dev Helper for Extension actions after all __buyShares() calls are made
function __buySharesCompletedHook(
address _caller,
uint256[] memory _sharesReceivedAmounts,
uint256 _gav
) private {
IPolicyManager(POLICY_MANAGER).validatePolicies(
address(this),
IPolicyManager.PolicyHook.BuySharesCompleted,
abi.encode(_caller, _sharesReceivedAmounts, _gav)
);
IFeeManager(FEE_MANAGER).invokeHook(
IFeeManager.FeeHook.BuySharesCompleted,
abi.encode(_caller, _sharesReceivedAmounts),
_gav
);
}
/// @dev Helper for Extension actions before any __buyShares() calls are made
function __buySharesSetupHook(
address _caller,
uint256[] memory _investmentAmounts,
uint256 _gav
) private {
IPolicyManager(POLICY_MANAGER).validatePolicies(
address(this),
IPolicyManager.PolicyHook.BuySharesSetup,
abi.encode(_caller, _investmentAmounts, _gav)
);
IFeeManager(FEE_MANAGER).invokeHook(
IFeeManager.FeeHook.BuySharesSetup,
abi.encode(_caller, _investmentAmounts),
_gav
);
}
/// @dev Helper for Extension actions immediately prior to issuing shares.
/// This could be cleaned up so both Extensions take the same encoded args and handle GAV
/// in the same way, but there is not the obvious need for gas savings of recycling
/// the GAV value for the current policies as there is for the fees.
function __preBuySharesHook(
address _buyer,
uint256 _investmentAmount,
uint256 _minSharesQuantity,
uint256 _gav
) private {
IFeeManager(FEE_MANAGER).invokeHook(
IFeeManager.FeeHook.PreBuyShares,
abi.encode(_buyer, _investmentAmount, _minSharesQuantity),
_gav
);
IPolicyManager(POLICY_MANAGER).validatePolicies(
address(this),
IPolicyManager.PolicyHook.PreBuyShares,
abi.encode(_buyer, _investmentAmount, _minSharesQuantity, _gav)
);
}
/// @dev Helper for Extension actions immediately after issuing shares.
/// Same comment applies from __preBuySharesHook() above.
function __postBuySharesHook(
address _buyer,
uint256 _investmentAmount,
uint256 _sharesIssued,
uint256 _preBuySharesGav
) private {
uint256 gav = _preBuySharesGav.add(_investmentAmount);
IFeeManager(FEE_MANAGER).invokeHook(
IFeeManager.FeeHook.PostBuyShares,
abi.encode(_buyer, _investmentAmount, _sharesIssued),
gav
);
IPolicyManager(POLICY_MANAGER).validatePolicies(
address(this),
IPolicyManager.PolicyHook.PostBuyShares,
abi.encode(_buyer, _investmentAmount, _sharesIssued, gav)
);
}
// REDEEM SHARES
/// @notice Redeem all of the sender's shares for a proportionate slice of the fund's assets
/// @return payoutAssets_ The assets paid out to the redeemer
/// @return payoutAmounts_ The amount of each asset paid out to the redeemer
/// @dev See __redeemShares() for further detail
function redeemShares()
external
returns (address[] memory payoutAssets_, uint256[] memory payoutAmounts_)
{
return
__redeemShares(
msg.sender,
ERC20(vaultProxy).balanceOf(msg.sender),
new address[](0),
new address[](0)
);
}
/// @notice Redeem a specified quantity of the sender's shares for a proportionate slice of
/// the fund's assets, optionally specifying additional assets and assets to skip.
/// @param _sharesQuantity The quantity of shares to redeem
/// @param _additionalAssets Additional (non-tracked) assets to claim
/// @param _assetsToSkip Tracked assets to forfeit
/// @return payoutAssets_ The assets paid out to the redeemer
/// @return payoutAmounts_ The amount of each asset paid out to the redeemer
/// @dev Any claim to passed _assetsToSkip will be forfeited entirely. This should generally
/// only be exercised if a bad asset is causing redemption to fail.
function redeemSharesDetailed(
uint256 _sharesQuantity,
address[] calldata _additionalAssets,
address[] calldata _assetsToSkip
) external returns (address[] memory payoutAssets_, uint256[] memory payoutAmounts_) {
return __redeemShares(msg.sender, _sharesQuantity, _additionalAssets, _assetsToSkip);
}
/// @dev Helper to parse an array of payout assets during redemption, taking into account
/// additional assets and assets to skip. _assetsToSkip ignores _additionalAssets.
/// All input arrays are assumed to be unique.
function __parseRedemptionPayoutAssets(
address[] memory _trackedAssets,
address[] memory _additionalAssets,
address[] memory _assetsToSkip
) private pure returns (address[] memory payoutAssets_) {
address[] memory trackedAssetsToPayout = _trackedAssets.removeItems(_assetsToSkip);
if (_additionalAssets.length == 0) {
return trackedAssetsToPayout;
}
// Add additional assets. Duplicates of trackedAssets are ignored.
bool[] memory indexesToAdd = new bool[](_additionalAssets.length);
uint256 additionalItemsCount;
for (uint256 i; i < _additionalAssets.length; i++) {
if (!trackedAssetsToPayout.contains(_additionalAssets[i])) {
indexesToAdd[i] = true;
additionalItemsCount++;
}
}
if (additionalItemsCount == 0) {
return trackedAssetsToPayout;
}
payoutAssets_ = new address[](trackedAssetsToPayout.length.add(additionalItemsCount));
for (uint256 i; i < trackedAssetsToPayout.length; i++) {
payoutAssets_[i] = trackedAssetsToPayout[i];
}
uint256 payoutAssetsIndex = trackedAssetsToPayout.length;
for (uint256 i; i < _additionalAssets.length; i++) {
if (indexesToAdd[i]) {
payoutAssets_[payoutAssetsIndex] = _additionalAssets[i];
payoutAssetsIndex++;
}
}
return payoutAssets_;
}
/// @dev Helper for system actions immediately prior to redeeming shares.
/// Policy validation is not currently allowed on redemption, to ensure continuous redeemability.
function __preRedeemSharesHook(address _redeemer, uint256 _sharesQuantity)
private
allowsPermissionedVaultAction
{
try
IFeeManager(FEE_MANAGER).invokeHook(
IFeeManager.FeeHook.PreRedeemShares,
abi.encode(_redeemer, _sharesQuantity),
0
)
{} catch (bytes memory reason) {
emit PreRedeemSharesHookFailed(reason, _redeemer, _sharesQuantity);
}
}
/// @dev Helper to redeem shares.
/// This function should never fail without a way to bypass the failure, which is assured
/// through two mechanisms:
/// 1. The FeeManager is called with the try/catch pattern to assure that calls to it
/// can never block redemption.
/// 2. If a token fails upon transfer(), that token can be skipped (and its balance forfeited)
/// by explicitly specifying _assetsToSkip.
/// Because of these assurances, shares should always be redeemable, with the exception
/// of the timelock period on shares actions that must be respected.
function __redeemShares(
address _redeemer,
uint256 _sharesQuantity,
address[] memory _additionalAssets,
address[] memory _assetsToSkip
)
private
locksReentrance
returns (address[] memory payoutAssets_, uint256[] memory payoutAmounts_)
{
require(_sharesQuantity > 0, "__redeemShares: _sharesQuantity must be >0");
require(
_additionalAssets.isUniqueSet(),
"__redeemShares: _additionalAssets contains duplicates"
);
require(_assetsToSkip.isUniqueSet(), "__redeemShares: _assetsToSkip contains duplicates");
IVault vaultProxyContract = IVault(vaultProxy);
// Only apply the sharesActionTimelock when a migration is not pending
if (!IDispatcher(DISPATCHER).hasMigrationRequest(address(vaultProxyContract))) {
__assertSharesActionNotTimelocked(_redeemer);
acctToLastSharesAction[_redeemer] = block.timestamp;
}
// When a fund is paused, settling fees will be skipped
if (!__fundIsPaused()) {
// Note that if a fee with `SettlementType.Direct` is charged here (i.e., not `Mint`),
// then those fee shares will be transferred from the user's balance rather
// than reallocated from the sharesQuantity being redeemed.
__preRedeemSharesHook(_redeemer, _sharesQuantity);
}
// Check the shares quantity against the user's balance after settling fees
ERC20 sharesContract = ERC20(address(vaultProxyContract));
require(
_sharesQuantity <= sharesContract.balanceOf(_redeemer),
"__redeemShares: Insufficient shares"
);
// Parse the payout assets given optional params to add or skip assets.
// Note that there is no validation that the _additionalAssets are known assets to
// the protocol. This means that the redeemer could specify a malicious asset,
// but since all state-changing, user-callable functions on this contract share the
// non-reentrant modifier, there is nowhere to perform a reentrancy attack.
payoutAssets_ = __parseRedemptionPayoutAssets(
vaultProxyContract.getTrackedAssets(),
_additionalAssets,
_assetsToSkip
);
require(payoutAssets_.length > 0, "__redeemShares: No payout assets");
// Destroy the shares.
// Must get the shares supply before doing so.
uint256 sharesSupply = sharesContract.totalSupply();
vaultProxyContract.burnShares(_redeemer, _sharesQuantity);
// Calculate and transfer payout asset amounts due to redeemer
payoutAmounts_ = new uint256[](payoutAssets_.length);
address denominationAssetCopy = denominationAsset;
for (uint256 i; i < payoutAssets_.length; i++) {
uint256 assetBalance = __finalizeIfSynthAndGetAssetBalance(
address(vaultProxyContract),
payoutAssets_[i],
true
);
// If all remaining shares are being redeemed, the logic changes slightly
if (_sharesQuantity == sharesSupply) {
payoutAmounts_[i] = assetBalance;
// Remove every tracked asset, except the denomination asset
if (payoutAssets_[i] != denominationAssetCopy) {
vaultProxyContract.removeTrackedAsset(payoutAssets_[i]);
}
} else {
payoutAmounts_[i] = assetBalance.mul(_sharesQuantity).div(sharesSupply);
}
// Transfer payout asset to redeemer
if (payoutAmounts_[i] > 0) {
vaultProxyContract.withdrawAssetTo(payoutAssets_[i], _redeemer, payoutAmounts_[i]);
}
}
emit SharesRedeemed(_redeemer, _sharesQuantity, payoutAssets_, payoutAmounts_);
return (payoutAssets_, payoutAmounts_);
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Gets the `denominationAsset` variable
/// @return denominationAsset_ The `denominationAsset` variable value
function getDenominationAsset() external view override returns (address denominationAsset_) {
return denominationAsset;
}
/// @notice Gets the routes for the various contracts used by all funds
/// @return dispatcher_ The `DISPATCHER` variable value
/// @return feeManager_ The `FEE_MANAGER` variable value
/// @return fundDeployer_ The `FUND_DEPLOYER` variable value
/// @return integrationManager_ The `INTEGRATION_MANAGER` variable value
/// @return policyManager_ The `POLICY_MANAGER` variable value
/// @return primitivePriceFeed_ The `PRIMITIVE_PRICE_FEED` variable value
/// @return valueInterpreter_ The `VALUE_INTERPRETER` variable value
function getLibRoutes()
external
view
returns (
address dispatcher_,
address feeManager_,
address fundDeployer_,
address integrationManager_,
address policyManager_,
address primitivePriceFeed_,
address valueInterpreter_
)
{
return (
DISPATCHER,
FEE_MANAGER,
FUND_DEPLOYER,
INTEGRATION_MANAGER,
POLICY_MANAGER,
PRIMITIVE_PRICE_FEED,
VALUE_INTERPRETER
);
}
/// @notice Gets the `overridePause` variable
/// @return overridePause_ The `overridePause` variable value
function getOverridePause() external view returns (bool overridePause_) {
return overridePause;
}
/// @notice Gets the `sharesActionTimelock` variable
/// @return sharesActionTimelock_ The `sharesActionTimelock` variable value
function getSharesActionTimelock() external view returns (uint256 sharesActionTimelock_) {
return sharesActionTimelock;
}
/// @notice Gets the `vaultProxy` variable
/// @return vaultProxy_ The `vaultProxy` variable value
function getVaultProxy() external view override returns (address vaultProxy_) {
return vaultProxy;
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IComptroller Interface
/// @author Enzyme Council <[email protected]>
interface IComptroller {
enum VaultAction {
None,
BurnShares,
MintShares,
TransferShares,
ApproveAssetSpender,
WithdrawAssetTo,
AddTrackedAsset,
RemoveTrackedAsset
}
function activate(address, bool) external;
function calcGav(bool) external returns (uint256, bool);
function calcGrossShareValue(bool) external returns (uint256, bool);
function callOnExtension(
address,
uint256,
bytes calldata
) external;
function configureExtensions(bytes calldata, bytes calldata) external;
function destruct() external;
function getDenominationAsset() external view returns (address);
function getVaultProxy() external view returns (address);
function init(address, uint256) external;
function permissionedVaultAction(VaultAction, bytes calldata) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "../../../../persistent/utils/IMigratableVault.sol";
/// @title IVault Interface
/// @author Enzyme Council <[email protected]>
interface IVault is IMigratableVault {
function addTrackedAsset(address) external;
function approveAssetSpender(
address,
address,
uint256
) external;
function burnShares(address, uint256) external;
function callOnContract(address, bytes calldata) external;
function getAccessor() external view returns (address);
function getOwner() external view returns (address);
function getTrackedAssets() external view returns (address[] memory);
function isTrackedAsset(address) external view returns (bool);
function mintShares(address, uint256) external;
function removeTrackedAsset(address) external;
function transferShares(
address,
address,
uint256
) external;
function withdrawAssetTo(
address,
address,
uint256
) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IExtension Interface
/// @author Enzyme Council <[email protected]>
/// @notice Interface for all extensions
interface IExtension {
function activateForFund(bool _isMigration) external;
function deactivateForFund() external;
function receiveCallFromComptroller(
address _comptrollerProxy,
uint256 _actionId,
bytes calldata _callArgs
) external;
function setConfigForFund(bytes calldata _configData) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/EnumerableSet.sol";
import "../../core/fund/comptroller/IComptroller.sol";
import "../../core/fund/vault/IVault.sol";
import "../../utils/AddressArrayLib.sol";
import "../utils/ExtensionBase.sol";
import "../utils/FundDeployerOwnerMixin.sol";
import "../utils/PermissionedVaultActionMixin.sol";
import "./IFee.sol";
import "./IFeeManager.sol";
/// @title FeeManager Contract
/// @author Enzyme Council <[email protected]>
/// @notice Manages fees for funds
contract FeeManager is
IFeeManager,
ExtensionBase,
FundDeployerOwnerMixin,
PermissionedVaultActionMixin
{
using AddressArrayLib for address[];
using EnumerableSet for EnumerableSet.AddressSet;
using SafeMath for uint256;
event AllSharesOutstandingForcePaidForFund(
address indexed comptrollerProxy,
address payee,
uint256 sharesDue
);
event FeeDeregistered(address indexed fee, string indexed identifier);
event FeeEnabledForFund(
address indexed comptrollerProxy,
address indexed fee,
bytes settingsData
);
event FeeRegistered(
address indexed fee,
string indexed identifier,
FeeHook[] implementedHooksForSettle,
FeeHook[] implementedHooksForUpdate,
bool usesGavOnSettle,
bool usesGavOnUpdate
);
event FeeSettledForFund(
address indexed comptrollerProxy,
address indexed fee,
SettlementType indexed settlementType,
address payer,
address payee,
uint256 sharesDue
);
event SharesOutstandingPaidForFund(
address indexed comptrollerProxy,
address indexed fee,
uint256 sharesDue
);
event FeesRecipientSetForFund(
address indexed comptrollerProxy,
address prevFeesRecipient,
address nextFeesRecipient
);
EnumerableSet.AddressSet private registeredFees;
mapping(address => bool) private feeToUsesGavOnSettle;
mapping(address => bool) private feeToUsesGavOnUpdate;
mapping(address => mapping(FeeHook => bool)) private feeToHookToImplementsSettle;
mapping(address => mapping(FeeHook => bool)) private feeToHookToImplementsUpdate;
mapping(address => address[]) private comptrollerProxyToFees;
mapping(address => mapping(address => uint256))
private comptrollerProxyToFeeToSharesOutstanding;
constructor(address _fundDeployer) public FundDeployerOwnerMixin(_fundDeployer) {}
// EXTERNAL FUNCTIONS
/// @notice Activate already-configured fees for use in the calling fund
function activateForFund(bool) external override {
address vaultProxy = __setValidatedVaultProxy(msg.sender);
address[] memory enabledFees = comptrollerProxyToFees[msg.sender];
for (uint256 i; i < enabledFees.length; i++) {
IFee(enabledFees[i]).activateForFund(msg.sender, vaultProxy);
}
}
/// @notice Deactivate fees for a fund
/// @dev msg.sender is validated during __invokeHook()
function deactivateForFund() external override {
// Settle continuous fees one last time, but without calling Fee.update()
__invokeHook(msg.sender, IFeeManager.FeeHook.Continuous, "", 0, false);
// Force payout of remaining shares outstanding
__forcePayoutAllSharesOutstanding(msg.sender);
// Clean up storage
__deleteFundStorage(msg.sender);
}
/// @notice Receives a dispatched `callOnExtension` from a fund's ComptrollerProxy
/// @param _actionId An ID representing the desired action
/// @param _callArgs Encoded arguments specific to the _actionId
/// @dev This is the only way to call a function on this contract that updates VaultProxy state.
/// For both of these actions, any caller is allowed, so we don't use the caller param.
function receiveCallFromComptroller(
address,
uint256 _actionId,
bytes calldata _callArgs
) external override {
if (_actionId == 0) {
// Settle and update all continuous fees
__invokeHook(msg.sender, IFeeManager.FeeHook.Continuous, "", 0, true);
} else if (_actionId == 1) {
__payoutSharesOutstandingForFees(msg.sender, _callArgs);
} else {
revert("receiveCallFromComptroller: Invalid _actionId");
}
}
/// @notice Enable and configure fees for use in the calling fund
/// @param _configData Encoded config data
/// @dev Caller is expected to be a valid ComptrollerProxy, but there isn't a need to validate.
/// The order of `fees` determines the order in which fees of the same FeeHook will be applied.
/// It is recommended to run ManagementFee before PerformanceFee in order to achieve precise
/// PerformanceFee calcs.
function setConfigForFund(bytes calldata _configData) external override {
(address[] memory fees, bytes[] memory settingsData) = abi.decode(
_configData,
(address[], bytes[])
);
// Sanity checks
require(
fees.length == settingsData.length,
"setConfigForFund: fees and settingsData array lengths unequal"
);
require(fees.isUniqueSet(), "setConfigForFund: fees cannot include duplicates");
// Enable each fee with settings
for (uint256 i; i < fees.length; i++) {
require(isRegisteredFee(fees[i]), "setConfigForFund: Fee is not registered");
// Set fund config on fee
IFee(fees[i]).addFundSettings(msg.sender, settingsData[i]);
// Enable fee for fund
comptrollerProxyToFees[msg.sender].push(fees[i]);
emit FeeEnabledForFund(msg.sender, fees[i], settingsData[i]);
}
}
/// @notice Allows all fees for a particular FeeHook to implement settle() and update() logic
/// @param _hook The FeeHook to invoke
/// @param _settlementData The encoded settlement parameters specific to the FeeHook
/// @param _gav The GAV for a fund if known in the invocating code, otherwise 0
function invokeHook(
FeeHook _hook,
bytes calldata _settlementData,
uint256 _gav
) external override {
__invokeHook(msg.sender, _hook, _settlementData, _gav, true);
}
// PRIVATE FUNCTIONS
/// @dev Helper to destroy local storage to get gas refund,
/// and to prevent further calls to fee manager
function __deleteFundStorage(address _comptrollerProxy) private {
delete comptrollerProxyToFees[_comptrollerProxy];
delete comptrollerProxyToVaultProxy[_comptrollerProxy];
}
/// @dev Helper to force the payout of shares outstanding across all fees.
/// For the current release, all shares in the VaultProxy are assumed to be
/// shares outstanding from fees. If not, then they were sent there by mistake
/// and are otherwise unrecoverable. We can therefore take the VaultProxy's
/// shares balance as the totalSharesOutstanding to payout to the fund owner.
function __forcePayoutAllSharesOutstanding(address _comptrollerProxy) private {
address vaultProxy = getVaultProxyForFund(_comptrollerProxy);
uint256 totalSharesOutstanding = ERC20(vaultProxy).balanceOf(vaultProxy);
if (totalSharesOutstanding == 0) {
return;
}
// Destroy any shares outstanding storage
address[] memory fees = comptrollerProxyToFees[_comptrollerProxy];
for (uint256 i; i < fees.length; i++) {
delete comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][fees[i]];
}
// Distribute all shares outstanding to the fees recipient
address payee = IVault(vaultProxy).getOwner();
__transferShares(_comptrollerProxy, vaultProxy, payee, totalSharesOutstanding);
emit AllSharesOutstandingForcePaidForFund(
_comptrollerProxy,
payee,
totalSharesOutstanding
);
}
/// @dev Helper to get the canonical value of GAV if not yet set and required by fee
function __getGavAsNecessary(
address _comptrollerProxy,
address _fee,
uint256 _gavOrZero
) private returns (uint256 gav_) {
if (_gavOrZero == 0 && feeUsesGavOnUpdate(_fee)) {
// Assumes that any fee that requires GAV would need to revert if invalid or not final
bool gavIsValid;
(gav_, gavIsValid) = IComptroller(_comptrollerProxy).calcGav(true);
require(gavIsValid, "__getGavAsNecessary: Invalid GAV");
} else {
gav_ = _gavOrZero;
}
return gav_;
}
/// @dev Helper to run settle() on all enabled fees for a fund that implement a given hook, and then to
/// optionally run update() on the same fees. This order allows fees an opportunity to update
/// their local state after all VaultProxy state transitions (i.e., minting, burning,
/// transferring shares) have finished. To optimize for the expensive operation of calculating
/// GAV, once one fee requires GAV, we recycle that `gav` value for subsequent fees.
/// Assumes that _gav is either 0 or has already been validated.
function __invokeHook(
address _comptrollerProxy,
FeeHook _hook,
bytes memory _settlementData,
uint256 _gavOrZero,
bool _updateFees
) private {
address[] memory fees = comptrollerProxyToFees[_comptrollerProxy];
if (fees.length == 0) {
return;
}
address vaultProxy = getVaultProxyForFund(_comptrollerProxy);
// This check isn't strictly necessary, but its cost is insignificant,
// and helps to preserve data integrity.
require(vaultProxy != address(0), "__invokeHook: Fund is not active");
// First, allow all fees to implement settle()
uint256 gav = __settleFees(
_comptrollerProxy,
vaultProxy,
fees,
_hook,
_settlementData,
_gavOrZero
);
// Second, allow fees to implement update()
// This function does not allow any further altering of VaultProxy state
// (i.e., burning, minting, or transferring shares)
if (_updateFees) {
__updateFees(_comptrollerProxy, vaultProxy, fees, _hook, _settlementData, gav);
}
}
/// @dev Helper to payout the shares outstanding for the specified fees.
/// Does not call settle() on fees.
/// Only callable via ComptrollerProxy.callOnExtension().
function __payoutSharesOutstandingForFees(address _comptrollerProxy, bytes memory _callArgs)
private
{
address[] memory fees = abi.decode(_callArgs, (address[]));
address vaultProxy = getVaultProxyForFund(msg.sender);
uint256 sharesOutstandingDue;
for (uint256 i; i < fees.length; i++) {
if (!IFee(fees[i]).payout(_comptrollerProxy, vaultProxy)) {
continue;
}
uint256 sharesOutstandingForFee
= comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][fees[i]];
if (sharesOutstandingForFee == 0) {
continue;
}
sharesOutstandingDue = sharesOutstandingDue.add(sharesOutstandingForFee);
// Delete shares outstanding and distribute from VaultProxy to the fees recipient
comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][fees[i]] = 0;
emit SharesOutstandingPaidForFund(_comptrollerProxy, fees[i], sharesOutstandingForFee);
}
if (sharesOutstandingDue > 0) {
__transferShares(
_comptrollerProxy,
vaultProxy,
IVault(vaultProxy).getOwner(),
sharesOutstandingDue
);
}
}
/// @dev Helper to settle a fee
function __settleFee(
address _comptrollerProxy,
address _vaultProxy,
address _fee,
FeeHook _hook,
bytes memory _settlementData,
uint256 _gav
) private {
(SettlementType settlementType, address payer, uint256 sharesDue) = IFee(_fee).settle(
_comptrollerProxy,
_vaultProxy,
_hook,
_settlementData,
_gav
);
if (settlementType == SettlementType.None) {
return;
}
address payee;
if (settlementType == SettlementType.Direct) {
payee = IVault(_vaultProxy).getOwner();
__transferShares(_comptrollerProxy, payer, payee, sharesDue);
} else if (settlementType == SettlementType.Mint) {
payee = IVault(_vaultProxy).getOwner();
__mintShares(_comptrollerProxy, payee, sharesDue);
} else if (settlementType == SettlementType.Burn) {
__burnShares(_comptrollerProxy, payer, sharesDue);
} else if (settlementType == SettlementType.MintSharesOutstanding) {
comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][_fee] = comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][_fee]
.add(sharesDue);
payee = _vaultProxy;
__mintShares(_comptrollerProxy, payee, sharesDue);
} else if (settlementType == SettlementType.BurnSharesOutstanding) {
comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][_fee] = comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][_fee]
.sub(sharesDue);
payer = _vaultProxy;
__burnShares(_comptrollerProxy, payer, sharesDue);
} else {
revert("__settleFee: Invalid SettlementType");
}
emit FeeSettledForFund(_comptrollerProxy, _fee, settlementType, payer, payee, sharesDue);
}
/// @dev Helper to settle fees that implement a given fee hook
function __settleFees(
address _comptrollerProxy,
address _vaultProxy,
address[] memory _fees,
FeeHook _hook,
bytes memory _settlementData,
uint256 _gavOrZero
) private returns (uint256 gav_) {
gav_ = _gavOrZero;
for (uint256 i; i < _fees.length; i++) {
if (!feeSettlesOnHook(_fees[i], _hook)) {
continue;
}
gav_ = __getGavAsNecessary(_comptrollerProxy, _fees[i], gav_);
__settleFee(_comptrollerProxy, _vaultProxy, _fees[i], _hook, _settlementData, gav_);
}
return gav_;
}
/// @dev Helper to update fees that implement a given fee hook
function __updateFees(
address _comptrollerProxy,
address _vaultProxy,
address[] memory _fees,
FeeHook _hook,
bytes memory _settlementData,
uint256 _gavOrZero
) private {
uint256 gav = _gavOrZero;
for (uint256 i; i < _fees.length; i++) {
if (!feeUpdatesOnHook(_fees[i], _hook)) {
continue;
}
gav = __getGavAsNecessary(_comptrollerProxy, _fees[i], gav);
IFee(_fees[i]).update(_comptrollerProxy, _vaultProxy, _hook, _settlementData, gav);
}
}
///////////////////
// FEES REGISTRY //
///////////////////
/// @notice Remove fees from the list of registered fees
/// @param _fees Addresses of fees to be deregistered
function deregisterFees(address[] calldata _fees) external onlyFundDeployerOwner {
require(_fees.length > 0, "deregisterFees: _fees cannot be empty");
for (uint256 i; i < _fees.length; i++) {
require(isRegisteredFee(_fees[i]), "deregisterFees: fee is not registered");
registeredFees.remove(_fees[i]);
emit FeeDeregistered(_fees[i], IFee(_fees[i]).identifier());
}
}
/// @notice Add fees to the list of registered fees
/// @param _fees Addresses of fees to be registered
/// @dev Stores the hooks that a fee implements and whether each implementation uses GAV,
/// which fronts the gas for calls to check if a hook is implemented, and guarantees
/// that these hook implementation return values do not change post-registration.
function registerFees(address[] calldata _fees) external onlyFundDeployerOwner {
require(_fees.length > 0, "registerFees: _fees cannot be empty");
for (uint256 i; i < _fees.length; i++) {
require(!isRegisteredFee(_fees[i]), "registerFees: fee already registered");
registeredFees.add(_fees[i]);
IFee feeContract = IFee(_fees[i]);
(
FeeHook[] memory implementedHooksForSettle,
FeeHook[] memory implementedHooksForUpdate,
bool usesGavOnSettle,
bool usesGavOnUpdate
) = feeContract.implementedHooks();
// Stores the hooks for which each fee implements settle() and update()
for (uint256 j; j < implementedHooksForSettle.length; j++) {
feeToHookToImplementsSettle[_fees[i]][implementedHooksForSettle[j]] = true;
}
for (uint256 j; j < implementedHooksForUpdate.length; j++) {
feeToHookToImplementsUpdate[_fees[i]][implementedHooksForUpdate[j]] = true;
}
// Stores whether each fee requires GAV during its implementations for settle() and update()
if (usesGavOnSettle) {
feeToUsesGavOnSettle[_fees[i]] = true;
}
if (usesGavOnUpdate) {
feeToUsesGavOnUpdate[_fees[i]] = true;
}
emit FeeRegistered(
_fees[i],
feeContract.identifier(),
implementedHooksForSettle,
implementedHooksForUpdate,
usesGavOnSettle,
usesGavOnUpdate
);
}
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Get a list of enabled fees for a given fund
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @return enabledFees_ An array of enabled fee addresses
function getEnabledFeesForFund(address _comptrollerProxy)
external
view
returns (address[] memory enabledFees_)
{
return comptrollerProxyToFees[_comptrollerProxy];
}
/// @notice Get the amount of shares outstanding for a particular fee for a fund
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _fee The fee address
/// @return sharesOutstanding_ The amount of shares outstanding
function getFeeSharesOutstandingForFund(address _comptrollerProxy, address _fee)
external
view
returns (uint256 sharesOutstanding_)
{
return comptrollerProxyToFeeToSharesOutstanding[_comptrollerProxy][_fee];
}
/// @notice Get all registered fees
/// @return registeredFees_ A list of all registered fee addresses
function getRegisteredFees() external view returns (address[] memory registeredFees_) {
registeredFees_ = new address[](registeredFees.length());
for (uint256 i; i < registeredFees_.length; i++) {
registeredFees_[i] = registeredFees.at(i);
}
return registeredFees_;
}
/// @notice Checks if a fee implements settle() on a particular hook
/// @param _fee The address of the fee to check
/// @param _hook The FeeHook to check
/// @return settlesOnHook_ True if the fee settles on the given hook
function feeSettlesOnHook(address _fee, FeeHook _hook)
public
view
returns (bool settlesOnHook_)
{
return feeToHookToImplementsSettle[_fee][_hook];
}
/// @notice Checks if a fee implements update() on a particular hook
/// @param _fee The address of the fee to check
/// @param _hook The FeeHook to check
/// @return updatesOnHook_ True if the fee updates on the given hook
function feeUpdatesOnHook(address _fee, FeeHook _hook)
public
view
returns (bool updatesOnHook_)
{
return feeToHookToImplementsUpdate[_fee][_hook];
}
/// @notice Checks if a fee uses GAV in its settle() implementation
/// @param _fee The address of the fee to check
/// @return usesGav_ True if the fee uses GAV during settle() implementation
function feeUsesGavOnSettle(address _fee) public view returns (bool usesGav_) {
return feeToUsesGavOnSettle[_fee];
}
/// @notice Checks if a fee uses GAV in its update() implementation
/// @param _fee The address of the fee to check
/// @return usesGav_ True if the fee uses GAV during update() implementation
function feeUsesGavOnUpdate(address _fee) public view returns (bool usesGav_) {
return feeToUsesGavOnUpdate[_fee];
}
/// @notice Check whether a fee is registered
/// @param _fee The address of the fee to check
/// @return isRegisteredFee_ True if the fee is registered
function isRegisteredFee(address _fee) public view returns (bool isRegisteredFee_) {
return registeredFees.contains(_fee);
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "./IFeeManager.sol";
/// @title Fee Interface
/// @author Enzyme Council <[email protected]>
/// @notice Interface for all fees
interface IFee {
function activateForFund(address _comptrollerProxy, address _vaultProxy) external;
function addFundSettings(address _comptrollerProxy, bytes calldata _settingsData) external;
function identifier() external pure returns (string memory identifier_);
function implementedHooks()
external
view
returns (
IFeeManager.FeeHook[] memory implementedHooksForSettle_,
IFeeManager.FeeHook[] memory implementedHooksForUpdate_,
bool usesGavOnSettle_,
bool usesGavOnUpdate_
);
function payout(address _comptrollerProxy, address _vaultProxy)
external
returns (bool isPayable_);
function settle(
address _comptrollerProxy,
address _vaultProxy,
IFeeManager.FeeHook _hook,
bytes calldata _settlementData,
uint256 _gav
)
external
returns (
IFeeManager.SettlementType settlementType_,
address payer_,
uint256 sharesDue_
);
function update(
address _comptrollerProxy,
address _vaultProxy,
IFeeManager.FeeHook _hook,
bytes calldata _settlementData,
uint256 _gav
) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
/// @title FeeManager Interface
/// @author Enzyme Council <[email protected]>
/// @notice Interface for the FeeManager
interface IFeeManager {
// No fees for the current release are implemented post-redeemShares
enum FeeHook {
Continuous,
BuySharesSetup,
PreBuyShares,
PostBuyShares,
BuySharesCompleted,
PreRedeemShares
}
enum SettlementType {None, Direct, Mint, Burn, MintSharesOutstanding, BurnSharesOutstanding}
function invokeHook(
FeeHook,
bytes calldata,
uint256
) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/SignedSafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../../../core/fund/comptroller/ComptrollerLib.sol";
import "../FeeManager.sol";
import "./utils/FeeBase.sol";
/// @title PerformanceFee Contract
/// @author Enzyme Council <[email protected]>
/// @notice A performance-based fee with configurable rate and crystallization period, using
/// a high watermark
/// @dev This contract assumes that all shares in the VaultProxy are shares outstanding,
/// which is fine for this release. Even if they are not, they are still shares that
/// are only claimable by the fund owner.
contract PerformanceFee is FeeBase {
using SafeMath for uint256;
using SignedSafeMath for int256;
event ActivatedForFund(address indexed comptrollerProxy, uint256 highWaterMark);
event FundSettingsAdded(address indexed comptrollerProxy, uint256 rate, uint256 period);
event LastSharePriceUpdated(
address indexed comptrollerProxy,
uint256 prevSharePrice,
uint256 nextSharePrice
);
event PaidOut(
address indexed comptrollerProxy,
uint256 prevHighWaterMark,
uint256 nextHighWaterMark,
uint256 aggregateValueDue
);
event PerformanceUpdated(
address indexed comptrollerProxy,
uint256 prevAggregateValueDue,
uint256 nextAggregateValueDue,
int256 sharesOutstandingDiff
);
struct FeeInfo {
uint256 rate;
uint256 period;
uint256 activated;
uint256 lastPaid;
uint256 highWaterMark;
uint256 lastSharePrice;
uint256 aggregateValueDue;
}
uint256 private constant RATE_DIVISOR = 10**18;
uint256 private constant SHARE_UNIT = 10**18;
mapping(address => FeeInfo) private comptrollerProxyToFeeInfo;
constructor(address _feeManager) public FeeBase(_feeManager) {}
// EXTERNAL FUNCTIONS
/// @notice Activates the fee for a fund
/// @param _comptrollerProxy The ComptrollerProxy of the fund
function activateForFund(address _comptrollerProxy, address) external override onlyFeeManager {
FeeInfo storage feeInfo = comptrollerProxyToFeeInfo[_comptrollerProxy];
// We must not force asset finality, otherwise funds that have Synths as tracked assets
// would be susceptible to a DoS attack when attempting to migrate to a release that uses
// this fee: an attacker trades a negligible amount of a tracked Synth with the VaultProxy
// as the recipient, thus causing `calcGrossShareValue(true)` to fail.
(uint256 grossSharePrice, bool sharePriceIsValid) = ComptrollerLib(_comptrollerProxy)
.calcGrossShareValue(false);
require(sharePriceIsValid, "activateForFund: Invalid share price");
feeInfo.highWaterMark = grossSharePrice;
feeInfo.lastSharePrice = grossSharePrice;
feeInfo.activated = block.timestamp;
emit ActivatedForFund(_comptrollerProxy, grossSharePrice);
}
/// @notice Add the initial fee settings for a fund
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _settingsData Encoded settings to apply to the policy for the fund
/// @dev `highWaterMark`, `lastSharePrice`, and `activated` are set during activation
function addFundSettings(address _comptrollerProxy, bytes calldata _settingsData)
external
override
onlyFeeManager
{
(uint256 feeRate, uint256 feePeriod) = abi.decode(_settingsData, (uint256, uint256));
require(feeRate > 0, "addFundSettings: feeRate must be greater than 0");
require(feePeriod > 0, "addFundSettings: feePeriod must be greater than 0");
comptrollerProxyToFeeInfo[_comptrollerProxy] = FeeInfo({
rate: feeRate,
period: feePeriod,
activated: 0,
lastPaid: 0,
highWaterMark: 0,
lastSharePrice: 0,
aggregateValueDue: 0
});
emit FundSettingsAdded(_comptrollerProxy, feeRate, feePeriod);
}
/// @notice Provides a constant string identifier for a fee
/// @return identifier_ The identifier string
function identifier() external pure override returns (string memory identifier_) {
return "PERFORMANCE";
}
/// @notice Gets the hooks that are implemented by the fee
/// @return implementedHooksForSettle_ The hooks during which settle() is implemented
/// @return implementedHooksForUpdate_ The hooks during which update() is implemented
/// @return usesGavOnSettle_ True if GAV is used during the settle() implementation
/// @return usesGavOnUpdate_ True if GAV is used during the update() implementation
/// @dev Used only during fee registration
function implementedHooks()
external
view
override
returns (
IFeeManager.FeeHook[] memory implementedHooksForSettle_,
IFeeManager.FeeHook[] memory implementedHooksForUpdate_,
bool usesGavOnSettle_,
bool usesGavOnUpdate_
)
{
implementedHooksForSettle_ = new IFeeManager.FeeHook[](3);
implementedHooksForSettle_[0] = IFeeManager.FeeHook.Continuous;
implementedHooksForSettle_[1] = IFeeManager.FeeHook.BuySharesSetup;
implementedHooksForSettle_[2] = IFeeManager.FeeHook.PreRedeemShares;
implementedHooksForUpdate_ = new IFeeManager.FeeHook[](3);
implementedHooksForUpdate_[0] = IFeeManager.FeeHook.Continuous;
implementedHooksForUpdate_[1] = IFeeManager.FeeHook.BuySharesCompleted;
implementedHooksForUpdate_[2] = IFeeManager.FeeHook.PreRedeemShares;
return (implementedHooksForSettle_, implementedHooksForUpdate_, true, true);
}
/// @notice Checks whether the shares outstanding for the fee can be paid out, and updates
/// the info for the fee's last payout
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @return isPayable_ True if shares outstanding can be paid out
function payout(address _comptrollerProxy, address)
external
override
onlyFeeManager
returns (bool isPayable_)
{
if (!payoutAllowed(_comptrollerProxy)) {
return false;
}
FeeInfo storage feeInfo = comptrollerProxyToFeeInfo[_comptrollerProxy];
feeInfo.lastPaid = block.timestamp;
uint256 prevHighWaterMark = feeInfo.highWaterMark;
uint256 nextHighWaterMark = __calcUint256Max(feeInfo.lastSharePrice, prevHighWaterMark);
uint256 prevAggregateValueDue = feeInfo.aggregateValueDue;
// Update state as necessary
if (prevAggregateValueDue > 0) {
feeInfo.aggregateValueDue = 0;
}
if (nextHighWaterMark > prevHighWaterMark) {
feeInfo.highWaterMark = nextHighWaterMark;
}
emit PaidOut(
_comptrollerProxy,
prevHighWaterMark,
nextHighWaterMark,
prevAggregateValueDue
);
return true;
}
/// @notice Settles the fee and calculates shares due
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _vaultProxy The VaultProxy of the fund
/// @param _gav The GAV of the fund
/// @return settlementType_ The type of settlement
/// @return (unused) The payer of shares due
/// @return sharesDue_ The amount of shares due
function settle(
address _comptrollerProxy,
address _vaultProxy,
IFeeManager.FeeHook,
bytes calldata,
uint256 _gav
)
external
override
onlyFeeManager
returns (
IFeeManager.SettlementType settlementType_,
address,
uint256 sharesDue_
)
{
if (_gav == 0) {
return (IFeeManager.SettlementType.None, address(0), 0);
}
int256 settlementSharesDue = __settleAndUpdatePerformance(
_comptrollerProxy,
_vaultProxy,
_gav
);
if (settlementSharesDue == 0) {
return (IFeeManager.SettlementType.None, address(0), 0);
} else if (settlementSharesDue > 0) {
// Settle by minting shares outstanding for custody
return (
IFeeManager.SettlementType.MintSharesOutstanding,
address(0),
uint256(settlementSharesDue)
);
} else {
// Settle by burning from shares outstanding
return (
IFeeManager.SettlementType.BurnSharesOutstanding,
address(0),
uint256(-settlementSharesDue)
);
}
}
/// @notice Updates the fee state after all fees have finished settle()
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _vaultProxy The VaultProxy of the fund
/// @param _hook The FeeHook being executed
/// @param _settlementData Encoded args to use in calculating the settlement
/// @param _gav The GAV of the fund
function update(
address _comptrollerProxy,
address _vaultProxy,
IFeeManager.FeeHook _hook,
bytes calldata _settlementData,
uint256 _gav
) external override onlyFeeManager {
uint256 prevSharePrice = comptrollerProxyToFeeInfo[_comptrollerProxy].lastSharePrice;
uint256 nextSharePrice = __calcNextSharePrice(
_comptrollerProxy,
_vaultProxy,
_hook,
_settlementData,
_gav
);
if (nextSharePrice == prevSharePrice) {
return;
}
comptrollerProxyToFeeInfo[_comptrollerProxy].lastSharePrice = nextSharePrice;
emit LastSharePriceUpdated(_comptrollerProxy, prevSharePrice, nextSharePrice);
}
// PUBLIC FUNCTIONS
/// @notice Checks whether the shares outstanding can be paid out
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @return payoutAllowed_ True if the fee payment is due
/// @dev Payout is allowed if fees have not yet been settled in a crystallization period,
/// and at least 1 crystallization period has passed since activation
function payoutAllowed(address _comptrollerProxy) public view returns (bool payoutAllowed_) {
FeeInfo memory feeInfo = comptrollerProxyToFeeInfo[_comptrollerProxy];
uint256 period = feeInfo.period;
uint256 timeSinceActivated = block.timestamp.sub(feeInfo.activated);
// Check if at least 1 crystallization period has passed since activation
if (timeSinceActivated < period) {
return false;
}
// Check that a full crystallization period has passed since the last payout
uint256 timeSincePeriodStart = timeSinceActivated % period;
uint256 periodStart = block.timestamp.sub(timeSincePeriodStart);
return feeInfo.lastPaid < periodStart;
}
// PRIVATE FUNCTIONS
/// @dev Helper to calculate the aggregated value accumulated to a fund since the last
/// settlement (happening at investment/redemption)
/// Validated:
/// _netSharesSupply > 0
/// _sharePriceWithoutPerformance != _prevSharePrice
function __calcAggregateValueDue(
uint256 _netSharesSupply,
uint256 _sharePriceWithoutPerformance,
uint256 _prevSharePrice,
uint256 _prevAggregateValueDue,
uint256 _feeRate,
uint256 _highWaterMark
) private pure returns (uint256) {
int256 superHWMValueSinceLastSettled = (
int256(__calcUint256Max(_highWaterMark, _sharePriceWithoutPerformance)).sub(
int256(__calcUint256Max(_highWaterMark, _prevSharePrice))
)
)
.mul(int256(_netSharesSupply))
.div(int256(SHARE_UNIT));
int256 valueDueSinceLastSettled = superHWMValueSinceLastSettled.mul(int256(_feeRate)).div(
int256(RATE_DIVISOR)
);
return
uint256(
__calcInt256Max(0, int256(_prevAggregateValueDue).add(valueDueSinceLastSettled))
);
}
/// @dev Helper to calculate the max of two int values
function __calcInt256Max(int256 _a, int256 _b) private pure returns (int256) {
if (_a >= _b) {
return _a;
}
return _b;
}
/// @dev Helper to calculate the next `lastSharePrice` value
function __calcNextSharePrice(
address _comptrollerProxy,
address _vaultProxy,
IFeeManager.FeeHook _hook,
bytes memory _settlementData,
uint256 _gav
) private view returns (uint256 nextSharePrice_) {
uint256 denominationAssetUnit = 10 **
uint256(ERC20(ComptrollerLib(_comptrollerProxy).getDenominationAsset()).decimals());
if (_gav == 0) {
return denominationAssetUnit;
}
// Get shares outstanding via VaultProxy balance and calc shares supply to get net shares supply
ERC20 vaultProxyContract = ERC20(_vaultProxy);
uint256 totalSharesSupply = vaultProxyContract.totalSupply();
uint256 nextNetSharesSupply = totalSharesSupply.sub(
vaultProxyContract.balanceOf(_vaultProxy)
);
if (nextNetSharesSupply == 0) {
return denominationAssetUnit;
}
uint256 nextGav = _gav;
// For both Continuous and BuySharesCompleted hooks, _gav and shares supply will not change,
// we only need additional calculations for PreRedeemShares
if (_hook == IFeeManager.FeeHook.PreRedeemShares) {
(, uint256 sharesDecrease) = __decodePreRedeemSharesSettlementData(_settlementData);
// Shares have not yet been burned
nextNetSharesSupply = nextNetSharesSupply.sub(sharesDecrease);
if (nextNetSharesSupply == 0) {
return denominationAssetUnit;
}
// Assets have not yet been withdrawn
uint256 gavDecrease = _gav.mul(sharesDecrease).div(totalSharesSupply);
nextGav = nextGav.sub(gavDecrease);
if (nextGav == 0) {
return denominationAssetUnit;
}
}
return nextGav.mul(SHARE_UNIT).div(nextNetSharesSupply);
}
/// @dev Helper to calculate the performance metrics for a fund.
/// Validated:
/// _totalSharesSupply > 0
/// _gav > 0
/// _totalSharesSupply != _totalSharesOutstanding
function __calcPerformance(
address _comptrollerProxy,
uint256 _totalSharesSupply,
uint256 _totalSharesOutstanding,
uint256 _prevAggregateValueDue,
FeeInfo memory feeInfo,
uint256 _gav
) private view returns (uint256 nextAggregateValueDue_, int256 sharesDue_) {
// Use the 'shares supply net shares outstanding' for performance calcs.
// Cannot be 0, as _totalSharesSupply != _totalSharesOutstanding
uint256 netSharesSupply = _totalSharesSupply.sub(_totalSharesOutstanding);
uint256 sharePriceWithoutPerformance = _gav.mul(SHARE_UNIT).div(netSharesSupply);
// If gross share price has not changed, can exit early
uint256 prevSharePrice = feeInfo.lastSharePrice;
if (sharePriceWithoutPerformance == prevSharePrice) {
return (_prevAggregateValueDue, 0);
}
nextAggregateValueDue_ = __calcAggregateValueDue(
netSharesSupply,
sharePriceWithoutPerformance,
prevSharePrice,
_prevAggregateValueDue,
feeInfo.rate,
feeInfo.highWaterMark
);
sharesDue_ = __calcSharesDue(
_comptrollerProxy,
netSharesSupply,
_gav,
nextAggregateValueDue_
);
return (nextAggregateValueDue_, sharesDue_);
}
/// @dev Helper to calculate sharesDue during settlement.
/// Validated:
/// _netSharesSupply > 0
/// _gav > 0
function __calcSharesDue(
address _comptrollerProxy,
uint256 _netSharesSupply,
uint256 _gav,
uint256 _nextAggregateValueDue
) private view returns (int256 sharesDue_) {
// If _nextAggregateValueDue > _gav, then no shares can be created.
// This is a known limitation of the model, which is only reached for unrealistically
// high performance fee rates (> 100%). A revert is allowed in such a case.
uint256 sharesDueForAggregateValueDue = _nextAggregateValueDue.mul(_netSharesSupply).div(
_gav.sub(_nextAggregateValueDue)
);
// Shares due is the +/- diff or the total shares outstanding already minted
return
int256(sharesDueForAggregateValueDue).sub(
int256(
FeeManager(FEE_MANAGER).getFeeSharesOutstandingForFund(
_comptrollerProxy,
address(this)
)
)
);
}
/// @dev Helper to calculate the max of two uint values
function __calcUint256Max(uint256 _a, uint256 _b) private pure returns (uint256) {
if (_a >= _b) {
return _a;
}
return _b;
}
/// @dev Helper to settle the fee and update performance state.
/// Validated:
/// _gav > 0
function __settleAndUpdatePerformance(
address _comptrollerProxy,
address _vaultProxy,
uint256 _gav
) private returns (int256 sharesDue_) {
ERC20 sharesTokenContract = ERC20(_vaultProxy);
uint256 totalSharesSupply = sharesTokenContract.totalSupply();
if (totalSharesSupply == 0) {
return 0;
}
uint256 totalSharesOutstanding = sharesTokenContract.balanceOf(_vaultProxy);
if (totalSharesOutstanding == totalSharesSupply) {
return 0;
}
FeeInfo storage feeInfo = comptrollerProxyToFeeInfo[_comptrollerProxy];
uint256 prevAggregateValueDue = feeInfo.aggregateValueDue;
uint256 nextAggregateValueDue;
(nextAggregateValueDue, sharesDue_) = __calcPerformance(
_comptrollerProxy,
totalSharesSupply,
totalSharesOutstanding,
prevAggregateValueDue,
feeInfo,
_gav
);
if (nextAggregateValueDue == prevAggregateValueDue) {
return 0;
}
// Update fee state
feeInfo.aggregateValueDue = nextAggregateValueDue;
emit PerformanceUpdated(
_comptrollerProxy,
prevAggregateValueDue,
nextAggregateValueDue,
sharesDue_
);
return sharesDue_;
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Gets the feeInfo for a given fund
/// @param _comptrollerProxy The ComptrollerProxy contract of the fund
/// @return feeInfo_ The feeInfo
function getFeeInfoForFund(address _comptrollerProxy)
external
view
returns (FeeInfo memory feeInfo_)
{
return comptrollerProxyToFeeInfo[_comptrollerProxy];
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "../../IFee.sol";
/// @title FeeBase Contract
/// @author Enzyme Council <[email protected]>
/// @notice Abstract base contract for all fees
abstract contract FeeBase is IFee {
address internal immutable FEE_MANAGER;
modifier onlyFeeManager {
require(msg.sender == FEE_MANAGER, "Only the FeeManger can make this call");
_;
}
constructor(address _feeManager) public {
FEE_MANAGER = _feeManager;
}
/// @notice Allows Fee to run logic during fund activation
/// @dev Unimplemented by default, may be overrode.
function activateForFund(address, address) external virtual override {
return;
}
/// @notice Runs payout logic for a fee that utilizes shares outstanding as its settlement type
/// @dev Returns false by default, can be overridden by fee
function payout(address, address) external virtual override returns (bool) {
return false;
}
/// @notice Update fee state after all settlement has occurred during a given fee hook
/// @dev Unimplemented by default, can be overridden by fee
function update(
address,
address,
IFeeManager.FeeHook,
bytes calldata,
uint256
) external virtual override {
return;
}
/// @notice Helper to parse settlement arguments from encoded data for PreBuyShares fee hook
function __decodePreBuySharesSettlementData(bytes memory _settlementData)
internal
pure
returns (
address buyer_,
uint256 investmentAmount_,
uint256 minSharesQuantity_
)
{
return abi.decode(_settlementData, (address, uint256, uint256));
}
/// @notice Helper to parse settlement arguments from encoded data for PreRedeemShares fee hook
function __decodePreRedeemSharesSettlementData(bytes memory _settlementData)
internal
pure
returns (address redeemer_, uint256 sharesQuantity_)
{
return abi.decode(_settlementData, (address, uint256));
}
/// @notice Helper to parse settlement arguments from encoded data for PostBuyShares fee hook
function __decodePostBuySharesSettlementData(bytes memory _settlementData)
internal
pure
returns (
address buyer_,
uint256 investmentAmount_,
uint256 sharesBought_
)
{
return abi.decode(_settlementData, (address, uint256, uint256));
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Gets the `FEE_MANAGER` variable
/// @return feeManager_ The `FEE_MANAGER` variable value
function getFeeManager() external view returns (address feeManager_) {
return FEE_MANAGER;
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
/// @title PolicyManager Interface
/// @author Enzyme Council <[email protected]>
/// @notice Interface for the PolicyManager
interface IPolicyManager {
enum PolicyHook {
BuySharesSetup,
PreBuyShares,
PostBuyShares,
BuySharesCompleted,
PreCallOnIntegration,
PostCallOnIntegration
}
function validatePolicies(
address,
PolicyHook,
bytes calldata
) external;
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "../../core/fund/comptroller/IComptroller.sol";
import "../../core/fund/vault/IVault.sol";
import "../IExtension.sol";
/// @title ExtensionBase Contract
/// @author Enzyme Council <[email protected]>
/// @notice Base class for an extension
abstract contract ExtensionBase is IExtension {
mapping(address => address) internal comptrollerProxyToVaultProxy;
/// @notice Allows extension to run logic during fund activation
/// @dev Unimplemented by default, may be overridden.
function activateForFund(bool) external virtual override {
return;
}
/// @notice Allows extension to run logic during fund deactivation (destruct)
/// @dev Unimplemented by default, may be overridden.
function deactivateForFund() external virtual override {
return;
}
/// @notice Receives calls from ComptrollerLib.callOnExtension()
/// and dispatches the appropriate action
/// @dev Unimplemented by default, may be overridden.
function receiveCallFromComptroller(
address,
uint256,
bytes calldata
) external virtual override {
revert("receiveCallFromComptroller: Unimplemented for Extension");
}
/// @notice Allows extension to run logic during fund configuration
/// @dev Unimplemented by default, may be overridden.
function setConfigForFund(bytes calldata) external virtual override {
return;
}
/// @dev Helper to validate a ComptrollerProxy-VaultProxy relation, which we store for both
/// gas savings and to guarantee a spoofed ComptrollerProxy does not change getVaultProxy().
/// Will revert without reason if the expected interfaces do not exist.
function __setValidatedVaultProxy(address _comptrollerProxy)
internal
returns (address vaultProxy_)
{
require(
comptrollerProxyToVaultProxy[_comptrollerProxy] == address(0),
"__setValidatedVaultProxy: Already set"
);
vaultProxy_ = IComptroller(_comptrollerProxy).getVaultProxy();
require(vaultProxy_ != address(0), "__setValidatedVaultProxy: Missing vaultProxy");
require(
_comptrollerProxy == IVault(vaultProxy_).getAccessor(),
"__setValidatedVaultProxy: Not the VaultProxy accessor"
);
comptrollerProxyToVaultProxy[_comptrollerProxy] = vaultProxy_;
return vaultProxy_;
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Gets the verified VaultProxy for a given ComptrollerProxy
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @return vaultProxy_ The VaultProxy of the fund
function getVaultProxyForFund(address _comptrollerProxy)
public
view
returns (address vaultProxy_)
{
return comptrollerProxyToVaultProxy[_comptrollerProxy];
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "../../core/fund-deployer/IFundDeployer.sol";
/// @title FundDeployerOwnerMixin Contract
/// @author Enzyme Council <[email protected]>
/// @notice A mixin contract that defers ownership to the owner of FundDeployer
abstract contract FundDeployerOwnerMixin {
address internal immutable FUND_DEPLOYER;
modifier onlyFundDeployerOwner() {
require(
msg.sender == getOwner(),
"onlyFundDeployerOwner: Only the FundDeployer owner can call this function"
);
_;
}
constructor(address _fundDeployer) public {
FUND_DEPLOYER = _fundDeployer;
}
/// @notice Gets the owner of this contract
/// @return owner_ The owner
/// @dev Ownership is deferred to the owner of the FundDeployer contract
function getOwner() public view returns (address owner_) {
return IFundDeployer(FUND_DEPLOYER).getOwner();
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Gets the `FUND_DEPLOYER` variable
/// @return fundDeployer_ The `FUND_DEPLOYER` variable value
function getFundDeployer() external view returns (address fundDeployer_) {
return FUND_DEPLOYER;
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "../../core/fund/comptroller/IComptroller.sol";
/// @title PermissionedVaultActionMixin Contract
/// @author Enzyme Council <[email protected]>
/// @notice A mixin contract for extensions that can make permissioned vault calls
abstract contract PermissionedVaultActionMixin {
/// @notice Adds a tracked asset to the fund
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _asset The asset to add
function __addTrackedAsset(address _comptrollerProxy, address _asset) internal {
IComptroller(_comptrollerProxy).permissionedVaultAction(
IComptroller.VaultAction.AddTrackedAsset,
abi.encode(_asset)
);
}
/// @notice Grants an allowance to a spender to use a fund's asset
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _asset The asset for which to grant an allowance
/// @param _target The spender of the allowance
/// @param _amount The amount of the allowance
function __approveAssetSpender(
address _comptrollerProxy,
address _asset,
address _target,
uint256 _amount
) internal {
IComptroller(_comptrollerProxy).permissionedVaultAction(
IComptroller.VaultAction.ApproveAssetSpender,
abi.encode(_asset, _target, _amount)
);
}
/// @notice Burns fund shares for a particular account
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _target The account for which to burn shares
/// @param _amount The amount of shares to burn
function __burnShares(
address _comptrollerProxy,
address _target,
uint256 _amount
) internal {
IComptroller(_comptrollerProxy).permissionedVaultAction(
IComptroller.VaultAction.BurnShares,
abi.encode(_target, _amount)
);
}
/// @notice Mints fund shares to a particular account
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _target The account to which to mint shares
/// @param _amount The amount of shares to mint
function __mintShares(
address _comptrollerProxy,
address _target,
uint256 _amount
) internal {
IComptroller(_comptrollerProxy).permissionedVaultAction(
IComptroller.VaultAction.MintShares,
abi.encode(_target, _amount)
);
}
/// @notice Removes a tracked asset from the fund
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _asset The asset to remove
function __removeTrackedAsset(address _comptrollerProxy, address _asset) internal {
IComptroller(_comptrollerProxy).permissionedVaultAction(
IComptroller.VaultAction.RemoveTrackedAsset,
abi.encode(_asset)
);
}
/// @notice Transfers fund shares from one account to another
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _from The account from which to transfer shares
/// @param _to The account to which to transfer shares
/// @param _amount The amount of shares to transfer
function __transferShares(
address _comptrollerProxy,
address _from,
address _to,
uint256 _amount
) internal {
IComptroller(_comptrollerProxy).permissionedVaultAction(
IComptroller.VaultAction.TransferShares,
abi.encode(_from, _to, _amount)
);
}
/// @notice Withdraws an asset from the VaultProxy to a given account
/// @param _comptrollerProxy The ComptrollerProxy of the fund
/// @param _asset The asset to withdraw
/// @param _target The account to which to withdraw the asset
/// @param _amount The amount of asset to withdraw
function __withdrawAssetTo(
address _comptrollerProxy,
address _asset,
address _target,
uint256 _amount
) internal {
IComptroller(_comptrollerProxy).permissionedVaultAction(
IComptroller.VaultAction.WithdrawAssetTo,
abi.encode(_asset, _target, _amount)
);
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IDerivativePriceFeed Interface
/// @author Enzyme Council <[email protected]>
/// @notice Simple interface for derivative price source oracle implementations
interface IDerivativePriceFeed {
function calcUnderlyingValues(address, uint256)
external
returns (address[] memory, uint256[] memory);
function isSupportedAsset(address) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../../../../extensions/utils/FundDeployerOwnerMixin.sol";
import "../../../../interfaces/ISynthetix.sol";
import "../../../../interfaces/ISynthetixAddressResolver.sol";
import "../../../../interfaces/ISynthetixExchangeRates.sol";
import "../../../../interfaces/ISynthetixProxyERC20.sol";
import "../../../../interfaces/ISynthetixSynth.sol";
import "../IDerivativePriceFeed.sol";
/// @title SynthetixPriceFeed Contract
/// @author Enzyme Council <[email protected]>
/// @notice A price feed that uses Synthetix oracles as price sources
contract SynthetixPriceFeed is IDerivativePriceFeed, FundDeployerOwnerMixin {
using SafeMath for uint256;
event SynthAdded(address indexed synth, bytes32 currencyKey);
event SynthCurrencyKeyUpdated(
address indexed synth,
bytes32 prevCurrencyKey,
bytes32 nextCurrencyKey
);
uint256 private constant SYNTH_UNIT = 10**18;
address private immutable ADDRESS_RESOLVER;
address private immutable SUSD;
mapping(address => bytes32) private synthToCurrencyKey;
constructor(
address _fundDeployer,
address _addressResolver,
address _sUSD,
address[] memory _synths
) public FundDeployerOwnerMixin(_fundDeployer) {
ADDRESS_RESOLVER = _addressResolver;
SUSD = _sUSD;
address[] memory sUSDSynths = new address[](1);
sUSDSynths[0] = _sUSD;
__addSynths(sUSDSynths);
__addSynths(_synths);
}
/// @notice Converts a given amount of a derivative to its underlying asset values
/// @param _derivative The derivative to convert
/// @param _derivativeAmount The amount of the derivative to convert
/// @return underlyings_ The underlying assets for the _derivative
/// @return underlyingAmounts_ The amount of each underlying asset for the equivalent derivative amount
function calcUnderlyingValues(address _derivative, uint256 _derivativeAmount)
external
override
returns (address[] memory underlyings_, uint256[] memory underlyingAmounts_)
{
underlyings_ = new address[](1);
underlyings_[0] = SUSD;
underlyingAmounts_ = new uint256[](1);
bytes32 currencyKey = getCurrencyKeyForSynth(_derivative);
require(currencyKey != 0, "calcUnderlyingValues: _derivative is not supported");
address exchangeRates = ISynthetixAddressResolver(ADDRESS_RESOLVER).requireAndGetAddress(
"ExchangeRates",
"calcUnderlyingValues: Missing ExchangeRates"
);
(uint256 rate, bool isInvalid) = ISynthetixExchangeRates(exchangeRates).rateAndInvalid(
currencyKey
);
require(!isInvalid, "calcUnderlyingValues: _derivative rate is not valid");
underlyingAmounts_[0] = _derivativeAmount.mul(rate).div(SYNTH_UNIT);
return (underlyings_, underlyingAmounts_);
}
/// @notice Checks whether an asset is a supported primitive of the price feed
/// @param _asset The asset to check
/// @return isSupported_ True if the asset is a supported primitive
function isSupportedAsset(address _asset) public view override returns (bool isSupported_) {
return getCurrencyKeyForSynth(_asset) != 0;
}
/////////////////////
// SYNTHS REGISTRY //
/////////////////////
/// @notice Adds Synths to the price feed
/// @param _synths Synths to add
function addSynths(address[] calldata _synths) external onlyFundDeployerOwner {
require(_synths.length > 0, "addSynths: Empty _synths");
__addSynths(_synths);
}
/// @notice Updates the cached currencyKey value for specified Synths
/// @param _synths Synths to update
/// @dev Anybody can call this function
function updateSynthCurrencyKeys(address[] calldata _synths) external {
require(_synths.length > 0, "updateSynthCurrencyKeys: Empty _synths");
for (uint256 i; i < _synths.length; i++) {
bytes32 prevCurrencyKey = synthToCurrencyKey[_synths[i]];
require(prevCurrencyKey != 0, "updateSynthCurrencyKeys: Synth not set");
bytes32 nextCurrencyKey = __getCurrencyKey(_synths[i]);
require(
nextCurrencyKey != prevCurrencyKey,
"updateSynthCurrencyKeys: Synth has correct currencyKey"
);
synthToCurrencyKey[_synths[i]] = nextCurrencyKey;
emit SynthCurrencyKeyUpdated(_synths[i], prevCurrencyKey, nextCurrencyKey);
}
}
/// @dev Helper to add Synths
function __addSynths(address[] memory _synths) private {
for (uint256 i; i < _synths.length; i++) {
require(synthToCurrencyKey[_synths[i]] == 0, "__addSynths: Value already set");
bytes32 currencyKey = __getCurrencyKey(_synths[i]);
require(currencyKey != 0, "__addSynths: No currencyKey");
synthToCurrencyKey[_synths[i]] = currencyKey;
emit SynthAdded(_synths[i], currencyKey);
}
}
/// @dev Helper to query a currencyKey from Synthetix
function __getCurrencyKey(address _synthProxy) private view returns (bytes32 currencyKey_) {
return ISynthetixSynth(ISynthetixProxyERC20(_synthProxy).target()).currencyKey();
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Gets the `ADDRESS_RESOLVER` variable
/// @return addressResolver_ The `ADDRESS_RESOLVER` variable value
function getAddressResolver() external view returns (address) {
return ADDRESS_RESOLVER;
}
/// @notice Gets the currencyKey for multiple given Synths
/// @return currencyKeys_ The currencyKey values
function getCurrencyKeysForSynths(address[] calldata _synths)
external
view
returns (bytes32[] memory currencyKeys_)
{
currencyKeys_ = new bytes32[](_synths.length);
for (uint256 i; i < _synths.length; i++) {
currencyKeys_[i] = synthToCurrencyKey[_synths[i]];
}
return currencyKeys_;
}
/// @notice Gets the `SUSD` variable
/// @return susd_ The `SUSD` variable value
function getSUSD() external view returns (address susd_) {
return SUSD;
}
/// @notice Gets the currencyKey for a given Synth
/// @return currencyKey_ The currencyKey value
function getCurrencyKeyForSynth(address _synth) public view returns (bytes32 currencyKey_) {
return synthToCurrencyKey[_synth];
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IPrimitivePriceFeed Interface
/// @author Enzyme Council <[email protected]>
/// @notice Interface for primitive price feeds
interface IPrimitivePriceFeed {
function calcCanonicalValue(
address,
uint256,
address
) external view returns (uint256, bool);
function calcLiveValue(
address,
uint256,
address
) external view returns (uint256, bool);
function isSupportedAsset(address) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title IValueInterpreter interface
/// @author Enzyme Council <[email protected]>
/// @notice Interface for ValueInterpreter
interface IValueInterpreter {
function calcCanonicalAssetValue(
address,
uint256,
address
) external returns (uint256, bool);
function calcCanonicalAssetsTotalValue(
address[] calldata,
uint256[] calldata,
address
) external returns (uint256, bool);
function calcLiveAssetValue(
address,
uint256,
address
) external returns (uint256, bool);
function calcLiveAssetsTotalValue(
address[] calldata,
uint256[] calldata,
address
) external returns (uint256, bool);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title ISynthetix Interface
/// @author Enzyme Council <[email protected]>
interface ISynthetix {
function exchangeOnBehalfWithTracking(
address,
bytes32,
uint256,
bytes32,
address,
bytes32
) external returns (uint256);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title ISynthetixAddressResolver Interface
/// @author Enzyme Council <[email protected]>
interface ISynthetixAddressResolver {
function requireAndGetAddress(bytes32, string calldata) external view returns (address);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title ISynthetixExchangeRates Interface
/// @author Enzyme Council <[email protected]>
interface ISynthetixExchangeRates {
function rateAndInvalid(bytes32) external view returns (uint256, bool);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title ISynthetixExchanger Interface
/// @author Enzyme Council <[email protected]>
interface ISynthetixExchanger {
function getAmountsForExchange(
uint256,
bytes32,
bytes32
)
external
view
returns (
uint256,
uint256,
uint256
);
function settle(address, bytes32)
external
returns (
uint256,
uint256,
uint256
);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title ISynthetixProxyERC20 Interface
/// @author Enzyme Council <[email protected]>
interface ISynthetixProxyERC20 {
function target() external view returns (address);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title ISynthetixSynth Interface
/// @author Enzyme Council <[email protected]>
interface ISynthetixSynth {
function currencyKey() external view returns (bytes32);
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
/// @title AddressArray Library
/// @author Enzyme Council <[email protected]>
/// @notice A library to extend the address array data type
library AddressArrayLib {
/// @dev Helper to add an item to an array. Does not assert uniqueness of the new item.
function addItem(address[] memory _self, address _itemToAdd)
internal
pure
returns (address[] memory nextArray_)
{
nextArray_ = new address[](_self.length + 1);
for (uint256 i; i < _self.length; i++) {
nextArray_[i] = _self[i];
}
nextArray_[_self.length] = _itemToAdd;
return nextArray_;
}
/// @dev Helper to add an item to an array, only if it is not already in the array.
function addUniqueItem(address[] memory _self, address _itemToAdd)
internal
pure
returns (address[] memory nextArray_)
{
if (contains(_self, _itemToAdd)) {
return _self;
}
return addItem(_self, _itemToAdd);
}
/// @dev Helper to verify if an array contains a particular value
function contains(address[] memory _self, address _target)
internal
pure
returns (bool doesContain_)
{
for (uint256 i; i < _self.length; i++) {
if (_target == _self[i]) {
return true;
}
}
return false;
}
/// @dev Helper to reassign all items in an array with a specified value
function fill(address[] memory _self, address _value)
internal
pure
returns (address[] memory nextArray_)
{
nextArray_ = new address[](_self.length);
for (uint256 i; i < nextArray_.length; i++) {
nextArray_[i] = _value;
}
return nextArray_;
}
/// @dev Helper to verify if array is a set of unique values.
/// Does not assert length > 0.
function isUniqueSet(address[] memory _self) internal pure returns (bool isUnique_) {
if (_self.length <= 1) {
return true;
}
uint256 arrayLength = _self.length;
for (uint256 i; i < arrayLength; i++) {
for (uint256 j = i + 1; j < arrayLength; j++) {
if (_self[i] == _self[j]) {
return false;
}
}
}
return true;
}
/// @dev Helper to remove items from an array. Removes all matching occurrences of each item.
/// Does not assert uniqueness of either array.
function removeItems(address[] memory _self, address[] memory _itemsToRemove)
internal
pure
returns (address[] memory nextArray_)
{
if (_itemsToRemove.length == 0) {
return _self;
}
bool[] memory indexesToRemove = new bool[](_self.length);
uint256 remainingItemsCount = _self.length;
for (uint256 i; i < _self.length; i++) {
if (contains(_itemsToRemove, _self[i])) {
indexesToRemove[i] = true;
remainingItemsCount--;
}
}
if (remainingItemsCount == _self.length) {
nextArray_ = _self;
} else if (remainingItemsCount > 0) {
nextArray_ = new address[](remainingItemsCount);
uint256 nextArrayIndex;
for (uint256 i; i < _self.length; i++) {
if (!indexesToRemove[i]) {
nextArray_[nextArrayIndex] = _self[i];
nextArrayIndex++;
}
}
}
return nextArray_;
}
}
// SPDX-License-Identifier: GPL-3.0
/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/
pragma solidity 0.6.12;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../infrastructure/price-feeds/derivatives/feeds/SynthetixPriceFeed.sol";
import "../interfaces/ISynthetixAddressResolver.sol";
import "../interfaces/ISynthetixExchanger.sol";
/// @title AssetFinalityResolver Contract
/// @author Enzyme Council <[email protected]>
/// @notice A contract that helps achieve asset finality
abstract contract AssetFinalityResolver {
address internal immutable SYNTHETIX_ADDRESS_RESOLVER;
address internal immutable SYNTHETIX_PRICE_FEED;
constructor(address _synthetixPriceFeed, address _synthetixAddressResolver) public {
SYNTHETIX_ADDRESS_RESOLVER = _synthetixAddressResolver;
SYNTHETIX_PRICE_FEED = _synthetixPriceFeed;
}
/// @dev Helper to finalize a Synth balance at a given target address and return its balance
function __finalizeIfSynthAndGetAssetBalance(
address _target,
address _asset,
bool _requireFinality
) internal returns (uint256 assetBalance_) {
bytes32 currencyKey = SynthetixPriceFeed(SYNTHETIX_PRICE_FEED).getCurrencyKeyForSynth(
_asset
);
if (currencyKey != 0) {
address synthetixExchanger = ISynthetixAddressResolver(SYNTHETIX_ADDRESS_RESOLVER)
.requireAndGetAddress(
"Exchanger",
"finalizeAndGetAssetBalance: Missing Exchanger"
);
try ISynthetixExchanger(synthetixExchanger).settle(_target, currencyKey) {} catch {
require(!_requireFinality, "finalizeAndGetAssetBalance: Cannot settle Synth");
}
}
return ERC20(_asset).balanceOf(_target);
}
///////////////////
// STATE GETTERS //
///////////////////
/// @notice Gets the `SYNTHETIX_ADDRESS_RESOLVER` variable
/// @return synthetixAddressResolver_ The `SYNTHETIX_ADDRESS_RESOLVER` variable value
function getSynthetixAddressResolver()
external
view
returns (address synthetixAddressResolver_)
{
return SYNTHETIX_ADDRESS_RESOLVER;
}
/// @notice Gets the `SYNTHETIX_PRICE_FEED` variable
/// @return synthetixPriceFeed_ The `SYNTHETIX_PRICE_FEED` variable value
function getSynthetixPriceFeed() external view returns (address synthetixPriceFeed_) {
return SYNTHETIX_PRICE_FEED;
}
}