Skip to content

Commit

Permalink
update IssuanceModule (#201)
Browse files Browse the repository at this point in the history
* update IssuanceModule

* Bump package to 0.1.14
  • Loading branch information
pblivin0x authored Mar 10, 2022
1 parent 9d1a311 commit a0647b9
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 726 deletions.
269 changes: 7 additions & 262 deletions contracts/protocol/modules/IssuanceModule.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 Set Labs Inc.
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -19,278 +19,23 @@
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";

import { DebtIssuanceModuleV2 } from "./DebtIssuanceModuleV2.sol";
import { IController } from "../../interfaces/IController.sol";
import { IManagerIssuanceHook } from "../../interfaces/IManagerIssuanceHook.sol";
import { IModuleIssuanceHook } from "../../interfaces/IModuleIssuanceHook.sol";
import { Invoke } from "../lib/Invoke.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { ModuleBase } from "../lib/ModuleBase.sol";
import { Position } from "../lib/Position.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";

/**
* @title IssuanceModule
* @author Set Protocol
*
* The IssuanceModule is a module that enables users to issue and redeem SetTokens that contain default and
* non-debt external Positions. Managers are able to set an external contract hook that is called before an
* issuance is called.
* The IssuanceModule is a module that enables users to issue and redeem SetTokens that contain default and all
* external positions, including debt positions. The manager can define arbitrary issuance logic in the manager
* hook, as well as specify issue and redeem fees. The manager can remove the module.
*/
contract IssuanceModule is ModuleBase, ReentrancyGuard {
using Invoke for ISetToken;
using Position for ISetToken;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;
using SignedSafeMath for int256;

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

event SetTokenIssued(address indexed _setToken, address _issuer, address _to, address _hookContract, uint256 _quantity);
event SetTokenRedeemed(address indexed _setToken, address _redeemer, address _to, uint256 _quantity);

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

// Mapping of SetToken to Issuance hook configurations
mapping(ISetToken => IManagerIssuanceHook) public managerIssuanceHook;
contract IssuanceModule is DebtIssuanceModuleV2 {

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

/**
* Set state controller state variable
*/
constructor(IController _controller) public ModuleBase(_controller) {}

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

/**
* Deposits components to the SetToken and replicates any external module component positions and mints
* the SetToken. Any issuances with SetTokens that have external positions with negative unit will revert.
*
* @param _setToken Instance of the SetToken contract
* @param _quantity Quantity of the SetToken to mint
* @param _to Address to mint SetToken to
*/
function issue(
ISetToken _setToken,
uint256 _quantity,
address _to
)
external
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
require(_quantity > 0, "Issue quantity must be > 0");

address hookContract = _callPreIssueHooks(_setToken, _quantity, msg.sender, _to);

(
address[] memory components,
uint256[] memory componentQuantities
) = getRequiredComponentIssuanceUnits(_setToken, _quantity, true);

// For each position, transfer the required underlying to the SetToken and call external module hooks
for (uint256 i = 0; i < components.length; i++) {
transferFrom(
IERC20(components[i]),
msg.sender,
address(_setToken),
componentQuantities[i]
);

_executeExternalPositionHooks(_setToken, _quantity, IERC20(components[i]), true);
}

_setToken.mint(_to, _quantity);

emit SetTokenIssued(address(_setToken), msg.sender, _to, hookContract, _quantity);
}

/**
* Burns a user's SetToken of specified quantity, unwinds external positions, and returns components
* to the specified address. Does not work for debt/negative external positions.
*
* @param _setToken Instance of the SetToken contract
* @param _quantity Quantity of the SetToken to redeem
* @param _to Address to send component assets to
*/
function redeem(
ISetToken _setToken,
uint256 _quantity,
address _to
)
external
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
require(_quantity > 0, "Redeem quantity must be > 0");

_setToken.burn(msg.sender, _quantity);

(
address[] memory components,
uint256[] memory componentQuantities
) = getRequiredComponentIssuanceUnits(_setToken, _quantity, false);

for (uint256 i = 0; i < components.length; i++) {
_executeExternalPositionHooks(_setToken, _quantity, IERC20(components[i]), false);

_setToken.strictInvokeTransfer(
components[i],
_to,
componentQuantities[i]
);
}

emit SetTokenRedeemed(address(_setToken), msg.sender, _to, _quantity);
}

/**
* Initializes this module to the SetToken with issuance-related hooks. Only callable by the SetToken's manager.
* Hook addresses are optional. Address(0) means that no hook will be called
*
* @param _setToken Instance of the SetToken to issue
* @param _preIssueHook Instance of the Manager Contract with the Pre-Issuance Hook function
*/
function initialize(
ISetToken _setToken,
IManagerIssuanceHook _preIssueHook
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndPendingSet(_setToken)
{
managerIssuanceHook[_setToken] = _preIssueHook;

_setToken.initializeModule();
}

/**
* Reverts as this module should not be removable after added. Users should always
* have a way to redeem their Sets
*/
function removeModule() external override {
revert("The IssuanceModule module cannot be removed");
}

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

/**
* Retrieves the addresses and units required to issue/redeem a particular quantity of SetToken.
*
* @param _setToken Instance of the SetToken to issue
* @param _quantity Quantity of SetToken to issue
* @param _isIssue Boolean whether the quantity is issuance or redemption
* @return address[] List of component addresses
* @return uint256[] List of component units required for a given SetToken quantity
*/
function getRequiredComponentIssuanceUnits(
ISetToken _setToken,
uint256 _quantity,
bool _isIssue
)
public
view
returns (address[] memory, uint256[] memory)
{
(
address[] memory components,
uint256[] memory issuanceUnits
) = _getTotalIssuanceUnits(_setToken);

uint256[] memory notionalUnits = new uint256[](components.length);
for (uint256 i = 0; i < issuanceUnits.length; i++) {
// Use preciseMulCeil to round up to ensure overcollateration when small issue quantities are provided
// and preciseMul to round down to ensure overcollateration when small redeem quantities are provided
notionalUnits[i] = _isIssue ?
issuanceUnits[i].preciseMulCeil(_quantity) :
issuanceUnits[i].preciseMul(_quantity);
}

return (components, notionalUnits);
}

/* ============ Internal Functions ============ */

/**
* Retrieves the component addresses and list of total units for components. This will revert if the external unit
* is ever equal or less than 0 .
*/
function _getTotalIssuanceUnits(ISetToken _setToken) internal view returns (address[] memory, uint256[] memory) {
address[] memory components = _setToken.getComponents();
uint256[] memory totalUnits = new uint256[](components.length);

for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
int256 cumulativeUnits = _setToken.getDefaultPositionRealUnit(component);

address[] memory externalModules = _setToken.getExternalPositionModules(component);
if (externalModules.length > 0) {
for (uint256 j = 0; j < externalModules.length; j++) {
int256 externalPositionUnit = _setToken.getExternalPositionRealUnit(component, externalModules[j]);

require(externalPositionUnit > 0, "Only positive external unit positions are supported");

cumulativeUnits = cumulativeUnits.add(externalPositionUnit);
}
}

totalUnits[i] = cumulativeUnits.toUint256();
}

return (components, totalUnits);
}

/**
* If a pre-issue hook has been configured, call the external-protocol contract. Pre-issue hook logic
* can contain arbitrary logic including validations, external function calls, etc.
* Note: All modules with external positions must implement ExternalPositionIssueHooks
*/
function _callPreIssueHooks(
ISetToken _setToken,
uint256 _quantity,
address _caller,
address _to
)
internal
returns(address)
{
IManagerIssuanceHook preIssueHook = managerIssuanceHook[_setToken];
if (address(preIssueHook) != address(0)) {
preIssueHook.invokePreIssueHook(_setToken, _quantity, _caller, _to);
return address(preIssueHook);
}

return address(0);
}

/**
* For each component's external module positions, calculate the total notional quantity, and
* call the module's issue hook or redeem hook.
* Note: It is possible that these hooks can cause the states of other modules to change.
* It can be problematic if the a hook called an external function that called back into a module, resulting in state inconsistencies.
*/
function _executeExternalPositionHooks(
ISetToken _setToken,
uint256 _setTokenQuantity,
IERC20 _component,
bool isIssue
)
internal
{
address[] memory externalPositionModules = _setToken.getExternalPositionModules(address(_component));
for (uint256 i = 0; i < externalPositionModules.length; i++) {
if (isIssue) {
IModuleIssuanceHook(externalPositionModules[i]).componentIssueHook(_setToken, _setTokenQuantity, _component, true);
} else {
IModuleIssuanceHook(externalPositionModules[i]).componentRedeemHook(_setToken, _setTokenQuantity, _component, true);
}
}
}
constructor(IController _controller) public DebtIssuanceModuleV2(_controller) {}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@setprotocol/set-protocol-v2",
"version": "0.1.13",
"version": "0.1.14",
"description": "",
"main": "dist",
"types": "dist/types",
Expand Down
Loading

0 comments on commit a0647b9

Please sign in to comment.