-
Notifications
You must be signed in to change notification settings - Fork 108
Expand file tree
/
Copy pathBasicIssuanceModule.sol
More file actions
259 lines (219 loc) · 9.1 KB
/
BasicIssuanceModule.sol
File metadata and controls
259 lines (219 loc) · 9.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
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 { 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 { IController } from "../../interfaces/IController.sol";
import { IManagerIssuanceHook } from "../../interfaces/IManagerIssuanceHook.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 BasicIssuanceModule
* @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.
*
* 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;
using Position for ISetToken.Position;
using Position for ISetToken;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;
/* ============ Events ============ */
event SetTokenIssued(
address indexed _setToken,
address indexed _issuer,
address indexed _to,
address _hookContract,
uint256 _quantity
);
event SetTokenRedeemed(
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;
/* ============ Constructor ============ */
/**
* Set state controller state variable
*
* @param _controller Address of controller contract
*/
constructor(IController _controller) public ModuleBase(_controller) {}
/* ============ External Functions ============ */
/**
* Deposits the SetToken's position components into the SetToken and mints the SetToken of the given quantity
* to the specified _to address. This function only handles Default Positions (positionState = 0).
*
* @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
) = getRequiredComponentUnitsForIssue(_setToken, _quantity);
// For each position, transfer the required underlying to the SetToken
for (uint256 i = 0; i < components.length; i++) {
// Transfer the component to the SetToken
transferFrom(
IERC20(components[i]),
msg.sender,
address(_setToken),
componentQuantities[i]
);
}
// Mint the SetToken
_setToken.mint(_to, _quantity);
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).
*
* @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
virtual
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
require(_quantity > 0, "Redeem quantity must be > 0");
// 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, address(0), _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 virtual override {
revert("The BasicIssuanceModule module cannot be removed");
}
/* ============ External Getter Functions ============ */
/**
* Retrieves the addresses and units required to mint a particular quantity of SetToken.
*
* @param _setToken Instance of the SetToken to issue
* @param _quantity Quantity of SetToken to issue
* @return address[] List of component addresses
* @return uint256[] List of component units required to issue the quantity of SetTokens
*/
function getRequiredComponentUnitsForIssue(
ISetToken _setToken,
uint256 _quantity
)
public
view
onlyValidAndInitializedSet(_setToken)
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);
}
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(
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);
}
}