Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update issuance modules #178

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions contracts/mocks/protocol/module/ManagerIssuanceHookMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,28 @@ pragma solidity 0.6.10;
import { ISetToken } from "../../../interfaces/ISetToken.sol";

contract ManagerIssuanceHookMock {
ISetToken public retrievedSetToken;

ISetToken public retrievedIssueSetToken;
uint256 public retrievedIssueQuantity;
address public retrievedSender;
address public retrievedTo;
address public retrievedIssueSender;
address public retrievedIssueTo;

ISetToken public retrievedRedeemSetToken;
uint256 public retrievedRedeemQuantity;
address public retrievedRedeemSender;
address public retrievedRedeemTo;

function invokePreIssueHook(ISetToken _setToken, uint256 _issueQuantity, address _sender, address _to) external {
retrievedSetToken = _setToken;
retrievedIssueSetToken = _setToken;
retrievedIssueQuantity = _issueQuantity;
retrievedSender = _sender;
retrievedTo = _to;
retrievedIssueSender = _sender;
retrievedIssueTo = _to;
}

function invokePreRedeemHook(ISetToken _setToken, uint256 _redeemQuantity, address _sender, address _to) external {
retrievedSetToken = _setToken;
retrievedIssueQuantity = _redeemQuantity;
retrievedSender = _sender;
retrievedTo = _to;
retrievedRedeemSetToken = _setToken;
retrievedRedeemQuantity = _redeemQuantity;
retrievedRedeemSender = _sender;
retrievedRedeemTo = _to;
}
}
15 changes: 13 additions & 2 deletions contracts/protocol/modules/BasicIssuanceModule.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
/*
Copyright 2020 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.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache License, Version 2.0
*/

Expand All @@ -34,6 +38,11 @@ import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
*
* Module that enables issuance and redemption functionality on a SetToken. This is a module that is
* required to bring the totalSupply of a Set above 0.
*
* CHANGELOG 12/15/2021:
* - update removeModule and redeem to be virtual
* - add _hookContract parameter to SetTokenRedeemed
* - always set _hookContract to address(0) when emitting SetTokenRedeemed
*/
contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
using Invoke for ISetToken;
Expand All @@ -56,6 +65,7 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
address indexed _setToken,
address indexed _redeemer,
address indexed _to,
address _hookContract,
uint256 _quantity
);

Expand Down Expand Up @@ -132,6 +142,7 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
address _to
)
external
virtual
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
Expand Down Expand Up @@ -159,7 +170,7 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
);
}

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

/**
Expand All @@ -186,7 +197,7 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
* Reverts as this module should not be removable after added. Users should always
* have a way to redeem their Sets
*/
function removeModule() external override {
function removeModule() external virtual override {
revert("The BasicIssuanceModule module cannot be removed");
}

Expand Down
155 changes: 155 additions & 0 deletions contracts/protocol/modules/BasicIssuanceModuleV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
Copyright 2020 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.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";

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 { BasicIssuanceModule } from "../../protocol/modules/BasicIssuanceModule.sol";
import { IController } from "../../interfaces/IController.sol";
import { IManagerIssuanceHook } from "../../interfaces/IManagerIssuanceHook.sol";
import { Invoke } from "../lib/Invoke.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { Position } from "../lib/Position.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";

/**
* @title BasicIssuanceModuleV2
* @author Set Protocol
*
* Module that enables issuance and redemption functionality on a SetToken. This is a module that is
* required to bring the totalSupply of a Set above 0.
*/
contract BasicIssuanceModuleV2 is BasicIssuanceModule {
using Invoke for ISetToken;
using Position for ISetToken.Position;
using Position for ISetToken;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;

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

/**
* Set state controller state variable
*
* @param _controller Address of controller contract
*/
constructor(IController _controller) public BasicIssuanceModule(_controller) {}

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

/**
* Redeems the SetToken's positions and sends the components of the given
* quantity to the caller. This function only handles Default Positions (positionState = 0).
*
* @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
override
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
require(_quantity > 0, "Redeem quantity must be > 0");

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

// Burn the SetToken - ERC20's internal burn already checks that the user has enough balance
_setToken.burn(msg.sender, _quantity);

// For each position, invoke the SetToken to transfer the tokens to the user
address[] memory components = _setToken.getComponents();
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
require(!_setToken.hasExternalPosition(component), "Only default positions are supported");

uint256 unit = _setToken.getDefaultPositionRealUnit(component).toUint256();

// Use preciseMul to round down to ensure overcollateration when small redeem quantities are provided
uint256 componentQuantity = _quantity.preciseMul(unit);

// Instruct the SetToken to transfer the component to the user
_setToken.strictInvokeTransfer(
component,
_to,
componentQuantity
);
}

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

/**
* SET TOKEN ONLY: Allows removal (and deletion of state) of BasicIssuanceModuleV2
*/
function removeModule() external override {
delete managerIssuanceHook[ISetToken(msg.sender)];
}

/**
* MANAGER ONLY: Updates the address of the manager issuance hook. To remove the hook
* set the new hook address to address(0)
*
* @param _setToken Instance of the SetToken to update manager hook
* @param _newHook New manager hook contract address
*/
function updateManagerIssuanceHook(
ISetToken _setToken,
IManagerIssuanceHook _newHook
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndInitializedSet(_setToken)
{
managerIssuanceHook[_setToken] = _newHook;
}

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

/**
* If a pre-issue hook has been configured, call the external-protocol contract's pre-redeem function.
* Pre-issue hook logic can contain arbitrary logic including validations, external function calls, etc.
*/
function _callPreRedeemHooks(
ISetToken _setToken,
uint256 _quantity,
address _caller,
address _to
)
internal
returns(address)
{
IManagerIssuanceHook preIssueHook = managerIssuanceHook[_setToken];
if (address(preIssueHook) != address(0)) {
preIssueHook.invokePreRedeemHook(_setToken, _quantity, _caller, _to);
return address(preIssueHook);
}

return address(0);
}
}
49 changes: 49 additions & 0 deletions contracts/protocol/modules/DebtIssuanceModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
* external positions, including debt positions. Module hooks are added to allow for syncing of positions, and component
* level hooks are added to ensure positions are replicated correctly. The manager can define arbitrary issuance logic
* in the manager hook, as well as specify issue and redeem fees.
*
* CHANGELOG 12/15/2021:
* - add _hookContract parameter to SetTokenRedeemed
* - call manager pre-redeem hooks on redemption
* - add function for updating the manager hooks
*/
contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {

Expand Down Expand Up @@ -75,6 +80,7 @@ contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
ISetToken indexed _setToken,
address indexed _redeemer,
address indexed _to,
address _hookContract,
uint256 _quantity,
uint256 _managerFee,
uint256 _protocolFee
Expand Down Expand Up @@ -173,6 +179,8 @@ contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
{
require(_quantity > 0, "Redeem quantity must be > 0");

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

_callModulePreRedeemHooks(_setToken, _quantity);

// Place burn after pre-redeem hooks because burning tokens may lead to false accounting of synced positions
Expand All @@ -198,12 +206,31 @@ contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
_setToken,
msg.sender,
_to,
hookContract,
_quantity,
managerFee,
protocolFee
);
}

/**
* MANAGER ONLY: Updates the address of the manager issuance hook. To remove the hook
* set the new hook address to address(0)
*
* @param _setToken Instance of the SetToken to update manager hook
* @param _newHook New manager hook contract address
*/
function updateManagerIssuanceHook(
ISetToken _setToken,
IManagerIssuanceHook _newHook
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndInitializedSet(_setToken)
{
issuanceSettings[_setToken].managerIssuanceHook = _newHook;
}

/**
* MANAGER ONLY: Updates address receiving issue/redeem fees for a given SetToken.
*
Expand Down Expand Up @@ -647,6 +674,28 @@ contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {

return address(0);
}

/**
* 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.
*/
function _callManagerPreRedeemHooks(
ISetToken _setToken,
uint256 _quantity,
address _caller,
address _to
)
internal
returns(address)
{
IManagerIssuanceHook preIssueHook = issuanceSettings[_setToken].managerIssuanceHook;
if (address(preIssueHook) != address(0)) {
preIssueHook.invokePreRedeemHook(_setToken, _quantity, _caller, _to);
return address(preIssueHook);
}

return address(0);
}

/**
* Calls all modules that have registered with the DebtIssuanceModule that have a moduleIssueHook.
Expand Down
8 changes: 7 additions & 1 deletion contracts/protocol/modules/DebtIssuanceModuleV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ import { Position } from "../lib/Position.sol";
*
* The getRequiredComponentIssuanceUnits function on this module assumes that Default token balances will be synced on every issuance
* and redemption. If token balances are not being synced it will over-estimate the amount of tokens required to issue a Set.
*
* CHANGELOG 12/15/2021:
* - call manager pre-redeem hooks on redemption
*/
contract DebtIssuanceModuleV2 is DebtIssuanceModule {
using Position for uint256;

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

constructor(IController _controller) public DebtIssuanceModule(_controller) {}
Expand Down Expand Up @@ -151,6 +154,8 @@ contract DebtIssuanceModuleV2 is DebtIssuanceModule {
{
require(_quantity > 0, "Redeem quantity must be > 0");

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

_callModulePreRedeemHooks(_setToken, _quantity);

uint256 initialSetSupply = _setToken.totalSupply();
Expand Down Expand Up @@ -183,6 +188,7 @@ contract DebtIssuanceModuleV2 is DebtIssuanceModule {
_setToken,
msg.sender,
_to,
hookContract,
_quantity,
managerFee,
protocolFee
Expand Down
Loading