Skip to content
Draft
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
149 changes: 134 additions & 15 deletions external/contracts/set/BasicIssuanceModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
using SafeMath for uint256;
using SafeCast for int256;

/* ============ Structs ============ */

// NOTE: moduleIssuanceHooks uses address[] for compatibility with AddressArrayUtils library
struct IssuanceSettings {
IManagerIssuanceHook managerIssuanceHook; // Instance of manager defined hook, can hold arbitrary logic
address[] moduleIssuanceHooks; // Array of modules that are registered with this module
mapping(address => bool) isModuleHook; // Mapping of modules to if they've registered a hook
}

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

event SetTokenIssued(
Expand All @@ -56,13 +65,13 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
address indexed _setToken,
address indexed _redeemer,
address indexed _to,
address _hookContract,
uint256 _quantity
);

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

// Mapping of SetToken to Issuance hook configurations
mapping(ISetToken => IManagerIssuanceHook) public managerIssuanceHook;
mapping(ISetToken => IssuanceSettings) public issuanceSettings;

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

Expand Down Expand Up @@ -94,7 +103,9 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
{
require(_quantity > 0, "Issue quantity must be > 0");

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

_callModulePreIssueHooks(_setToken, _quantity);

(
address[] memory components,
Expand All @@ -118,7 +129,7 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
emit SetTokenIssued(address(_setToken), msg.sender, _to, hookContract, _quantity);
}

/**
/**
* Redeems the SetToken's positions and sends the components of the given
* quantity to the caller. This function only handles Default Positions (positionState = 0).
*
Expand All @@ -132,11 +143,16 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
address _to
)
external
override
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
require(_quantity > 0, "Redeem quantity must be > 0");

address hookContract = _callManagerPreRedeemHooks(_setToken, _quantity, msg.sender, _to);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DIM doesn't have manager pre redeem hooks. not sure if we need that even if we add module hooks.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, since neither DIM nor BIMv1 have this, I guess it wouldn't be needed. The only example of managerHooks I found in our tests is the max supply hook (see here) which only needs to be called at issuance.


_callModulePreRedeemHooks(_setToken, _quantity);

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

Expand All @@ -159,7 +175,7 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
);
}

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

/**
Expand All @@ -183,11 +199,37 @@ 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
* SET TOKEN ONLY: Allows removal (and deletion of state) of BasicIssuanceModuleV2
*/
function removeModule() external override {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is not protected right ? So anyone can call it ?

Copy link

@ckoopmann ckoopmann Sep 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind / False alarm: I just figured out that the method would fail if the caller is not a set token 👍

revert("The BasicIssuanceModule module cannot be removed");
require(issuanceSettings[ISetToken(msg.sender)].moduleIssuanceHooks.length == 0, "Registered modules must be removed.");
delete issuanceSettings[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(
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this functionality is new. Not sure if really needed. Could be excluded because we have module hooks?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do use the supplyCapHook which is a manager issuance hook, so could be good to have a way to update it.

ISetToken _setToken,
IManagerIssuanceHook _newHook
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndInitializedSet(_setToken)
{
managerIssuanceHook[_setToken] = _newHook;
}

function getModuleIssuanceHooks(ISetToken _setToken) external view returns(address[] memory) {
return issuanceSettings[_setToken].moduleIssuanceHooks;
}

function isModuleIssuanceHook(ISetToken _setToken, address _hook) external view returns(bool) {
return issuanceSettings[_setToken].isModuleHook[_hook];
}

/* ============ External Getter Functions ============ */
Expand All @@ -200,35 +242,70 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
* @return address[] List of component addresses
* @return uint256[] List of component units required to issue the quantity of SetTokens
*/
function getRequiredComponentUnitsForIssue(
function getRequiredComponentIssuanceUnits(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice. Having different names for this method between BIM and DIM was really annoying before :+1

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just noticed that they also differ in returned values...
ideally BIMv2 would have to return an empty array as return param 3 (for debt positions) I think

ISetToken _setToken,
uint256 _quantity
)
public
view
onlyValidAndInitializedSet(_setToken)
returns (address[] memory, uint256[] memory)
{
return _calculateRequiredComponentIssuanceUnits(_setToken, totalQuantity, true);
}

/**
* Calculates the amount of each component will be returned on redemption.
* redeem Values DO NOT take into account any updates from pre action manager or module hooks.
*
* @param _setToken Instance of the SetToken to redeem
* @param _quantity Amount of Sets to be redeemed
*
* @return address[] Array of component addresses making up the Set
* @return uint256[] Array of equity notional amounts of each component, respectively, represented as uint256
*/
function getRequiredComponentRedemptionUnits(
ISetToken _setToken,
uint256 _quantity
)
external
view
virtual
returns (address[] memory, uint256[] memory)
{
return _calculateRequiredComponentIssuanceUnits(_setToken, totalQuantity, false);
}

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

function _calculateRequiredComponentIssuanceUnit (
ISetToken _setToken,
uint256 _quantity,
bool _isIssue
)
internal
returns (address[] memory, uint256[] memory)
{
address[] memory components = _setToken.getComponents();

uint256[] memory notionalUnits = new uint256[](components.length);

for (uint256 i = 0; i < components.length; i++) {
require(!_setToken.hasExternalPosition(components[i]), "Only default positions are supported");

notionalUnits[i] = _setToken.getDefaultPositionRealUnit(components[i]).toUint256().preciseMulCeil(_quantity);
uint256 units = _setToken.getDefaultPositionRealUnit(components[i]).toUint256();
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not exactly as in DIM, but almost

notionalUnits[i] = _isIssue ?
units.preciseMulCeil(_quantity) :
units.preciseMul(_quantity);
}

return (components, notionalUnits);
}

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

/**
* 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 _callPreIssueHooks(
function _callManagerPreIssueHooks(
ISetToken _setToken,
uint256 _quantity,
address _caller,
Expand All @@ -237,12 +314,54 @@ contract BasicIssuanceModule is ModuleBase, ReentrancyGuard {
internal
returns(address)
{
IManagerIssuanceHook preIssueHook = managerIssuanceHook[_setToken];
IManagerIssuanceHook preIssueHook = issuanceSettings[_setToken].managerIssuanceHook;
if (address(preIssueHook) != address(0)) {
preIssueHook.invokePreIssueHook(_setToken, _quantity, _caller, _to);
return address(preIssueHook);
}

return address(0);
}

/**
* If a pre-redeem 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 _callManagerPreRedeemHooks(
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, DIM doesn't call manager pre redeem hooks

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.
*/
function _callModulePreIssueHooks(ISetToken _setToken, uint256 _quantity) internal {
address[] memory issuanceHooks = issuanceSettings[_setToken].moduleIssuanceHooks;
for (uint256 i = 0; i < issuanceHooks.length; i++) {
IModuleIssuanceHook(issuanceHooks[i]).moduleIssueHook(_setToken, _quantity);
}
}

/**
* Calls all modules that have registered with the DebtIssuanceModule that have a moduleRedeemHook.
*/
function _callModulePreRedeemHooks(ISetToken _setToken, uint256 _quantity) internal {
address[] memory issuanceHooks = issuanceSettings[_setToken].moduleIssuanceHooks;
for (uint256 i = 0; i < issuanceHooks.length; i++) {
IModuleIssuanceHook(issuanceHooks[i]).moduleRedeemHook(_setToken, _quantity);
}
}
}