Skip to content

Commit 178b338

Browse files
committed
Revert "Rename DebtIssuanceModuleV2 to IssuanceModule"
This reverts commit 6684d25.
1 parent f453ce8 commit 178b338

File tree

6 files changed

+1451
-1366
lines changed

6 files changed

+1451
-1366
lines changed
Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
/*
2+
Copyright 2021 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
pragma experimental "ABIEncoderV2";
21+
22+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
23+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
24+
25+
import { DebtIssuanceModule } from "./DebtIssuanceModule.sol";
26+
import { IController } from "../../interfaces/IController.sol";
27+
import { Invoke } from "../lib/Invoke.sol";
28+
import { ISetToken } from "../../interfaces/ISetToken.sol";
29+
import { IssuanceValidationUtils } from "../lib/IssuanceValidationUtils.sol";
30+
import { Position } from "../lib/Position.sol";
31+
32+
/**
33+
* @title DebtIssuanceModuleV2
34+
* @author Set Protocol
35+
*
36+
* The DebtIssuanceModuleV2 is a module that enables users to issue and redeem SetTokens that contain default and all
37+
* external positions, including debt positions. Module hooks are added to allow for syncing of positions, and component
38+
* level hooks are added to ensure positions are replicated correctly. The manager can define arbitrary issuance logic
39+
* in the manager hook, as well as specify issue and redeem fees.
40+
*
41+
* NOTE:
42+
* DebtIssuanceModule contract confirms increase/decrease in balance of component held by the SetToken after every transfer in/out
43+
* for each component during issuance/redemption. This contract replaces those strict checks with slightly looser checks which
44+
* ensure that the SetToken remains collateralized after every transfer in/out for each component during issuance/redemption.
45+
* This module should be used to issue/redeem SetToken whose one or more components return a balance value with +/-1 wei error.
46+
* For example, this module can be used to issue/redeem SetTokens which has one or more aTokens as its components.
47+
* The new checks do NOT apply to any transfers that are part of an external position. A token that has rounding issues may lead to
48+
* reverts if it is included as an external position unless explicitly allowed in a module hook.
49+
*
50+
* The getRequiredComponentIssuanceUnits function on this module assumes that Default token balances will be synced on every issuance
51+
* and redemption. If token balances are not being synced it will over-estimate the amount of tokens required to issue a Set.
52+
*/
53+
contract DebtIssuanceModuleV2 is DebtIssuanceModule {
54+
using Position for uint256;
55+
56+
/* ============ Constructor ============ */
57+
58+
constructor(IController _controller) public DebtIssuanceModule(_controller) {}
59+
60+
/* ============ External Functions ============ */
61+
62+
/**
63+
* Deposits components to the SetToken, replicates any external module component positions and mints
64+
* the SetToken. If the token has a debt position all collateral will be transferred in first then debt
65+
* will be returned to the minting address. If specified, a fee will be charged on issuance.
66+
*
67+
* NOTE: Overrides DebtIssuanceModule#issue external function and adds undercollateralization checks in place of the
68+
* previous default strict balances checks. The undercollateralization checks are implemented in IssuanceValidationUtils library and they
69+
* revert upon undercollateralization of the SetToken post component transfer.
70+
*
71+
* @param _setToken Instance of the SetToken to issue
72+
* @param _quantity Quantity of SetToken to issue
73+
* @param _to Address to mint SetToken to
74+
*/
75+
function issue(
76+
ISetToken _setToken,
77+
uint256 _quantity,
78+
address _to
79+
)
80+
external
81+
override
82+
nonReentrant
83+
onlyValidAndInitializedSet(_setToken)
84+
{
85+
require(_quantity > 0, "Issue quantity must be > 0");
86+
87+
address hookContract = _callManagerPreIssueHooks(_setToken, _quantity, msg.sender, _to);
88+
89+
_callModulePreIssueHooks(_setToken, _quantity);
90+
91+
92+
uint256 initialSetSupply = _setToken.totalSupply();
93+
94+
(
95+
uint256 quantityWithFees,
96+
uint256 managerFee,
97+
uint256 protocolFee
98+
) = calculateTotalFees(_setToken, _quantity, true);
99+
100+
// Prevent stack too deep
101+
{
102+
(
103+
address[] memory components,
104+
uint256[] memory equityUnits,
105+
uint256[] memory debtUnits
106+
) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityWithFees, true);
107+
108+
uint256 finalSetSupply = initialSetSupply.add(quantityWithFees);
109+
110+
_resolveEquityPositions(_setToken, quantityWithFees, _to, true, components, equityUnits, initialSetSupply, finalSetSupply);
111+
_resolveDebtPositions(_setToken, quantityWithFees, true, components, debtUnits, initialSetSupply, finalSetSupply);
112+
_resolveFees(_setToken, managerFee, protocolFee);
113+
}
114+
115+
_setToken.mint(_to, _quantity);
116+
117+
emit SetTokenIssued(
118+
_setToken,
119+
msg.sender,
120+
_to,
121+
hookContract,
122+
_quantity,
123+
managerFee,
124+
protocolFee
125+
);
126+
}
127+
128+
/**
129+
* Returns components from the SetToken, unwinds any external module component positions and burns the SetToken.
130+
* If the token has debt positions, the module transfers in the required debt amounts from the caller and uses
131+
* those funds to repay the debts on behalf of the SetToken. All debt will be paid down first then equity positions
132+
* will be returned to the minting address. If specified, a fee will be charged on redeem.
133+
*
134+
* NOTE: Overrides DebtIssuanceModule#redeem internal function and adds undercollateralization checks in place of the
135+
* previous default strict balances checks. The undercollateralization checks are implemented in IssuanceValidationUtils library
136+
* and they revert upon undercollateralization of the SetToken post component transfer.
137+
*
138+
* @param _setToken Instance of the SetToken to redeem
139+
* @param _quantity Quantity of SetToken to redeem
140+
* @param _to Address to send collateral to
141+
*/
142+
function redeem(
143+
ISetToken _setToken,
144+
uint256 _quantity,
145+
address _to
146+
)
147+
external
148+
override
149+
nonReentrant
150+
onlyValidAndInitializedSet(_setToken)
151+
{
152+
require(_quantity > 0, "Redeem quantity must be > 0");
153+
154+
_callModulePreRedeemHooks(_setToken, _quantity);
155+
156+
uint256 initialSetSupply = _setToken.totalSupply();
157+
158+
// Place burn after pre-redeem hooks because burning tokens may lead to false accounting of synced positions
159+
_setToken.burn(msg.sender, _quantity);
160+
161+
(
162+
uint256 quantityNetFees,
163+
uint256 managerFee,
164+
uint256 protocolFee
165+
) = calculateTotalFees(_setToken, _quantity, false);
166+
167+
// Prevent stack too deep
168+
{
169+
(
170+
address[] memory components,
171+
uint256[] memory equityUnits,
172+
uint256[] memory debtUnits
173+
) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityNetFees, false);
174+
175+
uint256 finalSetSupply = initialSetSupply.sub(quantityNetFees);
176+
177+
_resolveDebtPositions(_setToken, quantityNetFees, false, components, debtUnits, initialSetSupply, finalSetSupply);
178+
_resolveEquityPositions(_setToken, quantityNetFees, _to, false, components, equityUnits, initialSetSupply, finalSetSupply);
179+
_resolveFees(_setToken, managerFee, protocolFee);
180+
}
181+
182+
emit SetTokenRedeemed(
183+
_setToken,
184+
msg.sender,
185+
_to,
186+
_quantity,
187+
managerFee,
188+
protocolFee
189+
);
190+
}
191+
192+
/* ============ External View Functions ============ */
193+
194+
/**
195+
* Calculates the amount of each component needed to collateralize passed issue quantity plus fees of Sets as well as amount of debt
196+
* that will be returned to caller. Default equity alues are calculated based on token balances and not position units in order to more
197+
* closely track any accrued tokens that will be synced during issuance. External equity and debt positions will use the stored position
198+
* units. IF TOKEN VALUES ARE NOT BEING SYNCED DURING ISSUANCE THIS FUNCTION WILL OVER ESTIMATE THE AMOUNT OF REQUIRED TOKENS.
199+
*
200+
* @param _setToken Instance of the SetToken to issue
201+
* @param _quantity Amount of Sets to be issued
202+
*
203+
* @return address[] Array of component addresses making up the Set
204+
* @return uint256[] Array of equity notional amounts of each component, respectively, represented as uint256
205+
* @return uint256[] Array of debt notional amounts of each component, respectively, represented as uint256
206+
*/
207+
function getRequiredComponentIssuanceUnits(
208+
ISetToken _setToken,
209+
uint256 _quantity
210+
)
211+
external
212+
view
213+
override
214+
returns (address[] memory, uint256[] memory, uint256[] memory)
215+
{
216+
(
217+
uint256 totalQuantity,,
218+
) = calculateTotalFees(_setToken, _quantity, true);
219+
220+
if(_setToken.totalSupply() == 0) {
221+
return _calculateRequiredComponentIssuanceUnits(_setToken, totalQuantity, true);
222+
} else {
223+
(
224+
address[] memory components,
225+
uint256[] memory equityUnits,
226+
uint256[] memory debtUnits
227+
) = _getTotalIssuanceUnitsFromBalances(_setToken);
228+
229+
uint256 componentsLength = components.length;
230+
uint256[] memory totalEquityUnits = new uint256[](componentsLength);
231+
uint256[] memory totalDebtUnits = new uint256[](componentsLength);
232+
for (uint256 i = 0; i < components.length; i++) {
233+
// Use preciseMulCeil to round up to ensure overcollateration of equity when small issue quantities are provided
234+
// and use preciseMul to round debt calculations down to make sure we don't return too much debt to issuer
235+
totalEquityUnits[i] = equityUnits[i].preciseMulCeil(totalQuantity);
236+
totalDebtUnits[i] = debtUnits[i].preciseMul(totalQuantity);
237+
}
238+
239+
return (components, totalEquityUnits, totalDebtUnits);
240+
}
241+
}
242+
243+
/* ============ Internal Functions ============ */
244+
245+
/**
246+
* Resolve equity positions associated with SetToken. On issuance, the total equity position for an asset (including default and external
247+
* positions) is transferred in. Then any external position hooks are called to transfer the external positions to their necessary place.
248+
* On redemption all external positions are recalled by the external position hook, then those position plus any default position are
249+
* transferred back to the _to address.
250+
*/
251+
function _resolveEquityPositions(
252+
ISetToken _setToken,
253+
uint256 _quantity,
254+
address _to,
255+
bool _isIssue,
256+
address[] memory _components,
257+
uint256[] memory _componentEquityQuantities,
258+
uint256 _initialSetSupply,
259+
uint256 _finalSetSupply
260+
)
261+
internal
262+
{
263+
for (uint256 i = 0; i < _components.length; i++) {
264+
address component = _components[i];
265+
uint256 componentQuantity = _componentEquityQuantities[i];
266+
if (componentQuantity > 0) {
267+
if (_isIssue) {
268+
// Call SafeERC20#safeTransferFrom instead of ExplicitERC20#transferFrom
269+
SafeERC20.safeTransferFrom(
270+
IERC20(component),
271+
msg.sender,
272+
address(_setToken),
273+
componentQuantity
274+
);
275+
276+
IssuanceValidationUtils.validateCollateralizationPostTransferInPreHook(_setToken, component, _initialSetSupply, componentQuantity);
277+
278+
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), true, true);
279+
} else {
280+
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), false, true);
281+
282+
// Call Invoke#invokeTransfer instead of Invoke#strictInvokeTransfer
283+
_setToken.invokeTransfer(component, _to, componentQuantity);
284+
285+
IssuanceValidationUtils.validateCollateralizationPostTransferOut(_setToken, component, _finalSetSupply);
286+
}
287+
}
288+
}
289+
}
290+
291+
/**
292+
* Resolve debt positions associated with SetToken. On issuance, debt positions are entered into by calling the external position hook. The
293+
* resulting debt is then returned to the calling address. On redemption, the module transfers in the required debt amount from the caller
294+
* and uses those funds to repay the debt on behalf of the SetToken.
295+
*/
296+
function _resolveDebtPositions(
297+
ISetToken _setToken,
298+
uint256 _quantity,
299+
bool _isIssue,
300+
address[] memory _components,
301+
uint256[] memory _componentDebtQuantities,
302+
uint256 _initialSetSupply,
303+
uint256 _finalSetSupply
304+
)
305+
internal
306+
{
307+
for (uint256 i = 0; i < _components.length; i++) {
308+
address component = _components[i];
309+
uint256 componentQuantity = _componentDebtQuantities[i];
310+
if (componentQuantity > 0) {
311+
if (_isIssue) {
312+
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), true, false);
313+
314+
// Call Invoke#invokeTransfer instead of Invoke#strictInvokeTransfer
315+
_setToken.invokeTransfer(component, msg.sender, componentQuantity);
316+
317+
IssuanceValidationUtils.validateCollateralizationPostTransferOut(_setToken, component, _finalSetSupply);
318+
} else {
319+
// Call SafeERC20#safeTransferFrom instead of ExplicitERC20#transferFrom
320+
SafeERC20.safeTransferFrom(
321+
IERC20(component),
322+
msg.sender,
323+
address(_setToken),
324+
componentQuantity
325+
);
326+
327+
IssuanceValidationUtils.validateCollateralizationPostTransferInPreHook(_setToken, component, _initialSetSupply, componentQuantity);
328+
329+
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), false, false);
330+
}
331+
}
332+
}
333+
}
334+
/**
335+
* Reimplementation of _getTotalIssuanceUnits but instead derives Default equity positions from token balances on Set instead of from
336+
* position units. This function is ONLY to be used in getRequiredComponentIssuanceUnits in order to return more accurate required
337+
* token amounts to issuers when positions are being synced on issuance.
338+
*
339+
* @param _setToken Instance of the SetToken to issue
340+
*
341+
* @return address[] Array of component addresses making up the Set
342+
* @return uint256[] Array of equity unit amounts of each component, respectively, represented as uint256
343+
* @return uint256[] Array of debt unit amounts of each component, respectively, represented as uint256
344+
*/
345+
function _getTotalIssuanceUnitsFromBalances(
346+
ISetToken _setToken
347+
)
348+
internal
349+
view
350+
returns (address[] memory, uint256[] memory, uint256[] memory)
351+
{
352+
address[] memory components = _setToken.getComponents();
353+
uint256 componentsLength = components.length;
354+
355+
uint256[] memory equityUnits = new uint256[](componentsLength);
356+
uint256[] memory debtUnits = new uint256[](componentsLength);
357+
358+
uint256 totalSupply = _setToken.totalSupply();
359+
360+
for (uint256 i = 0; i < components.length; i++) {
361+
address component = components[i];
362+
int256 cumulativeEquity = totalSupply
363+
.getDefaultPositionUnit(IERC20(component).balanceOf(address(_setToken)))
364+
.toInt256();
365+
int256 cumulativeDebt = 0;
366+
address[] memory externalPositions = _setToken.getExternalPositionModules(component);
367+
368+
if (externalPositions.length > 0) {
369+
for (uint256 j = 0; j < externalPositions.length; j++) {
370+
int256 externalPositionUnit = _setToken.getExternalPositionRealUnit(component, externalPositions[j]);
371+
372+
// If positionUnit <= 0 it will be "added" to debt position
373+
if (externalPositionUnit > 0) {
374+
cumulativeEquity = cumulativeEquity.add(externalPositionUnit);
375+
} else {
376+
cumulativeDebt = cumulativeDebt.add(externalPositionUnit);
377+
}
378+
}
379+
}
380+
381+
equityUnits[i] = cumulativeEquity.toUint256();
382+
debtUnits[i] = cumulativeDebt.mul(-1).toUint256();
383+
}
384+
385+
return (components, equityUnits, debtUnits);
386+
}
387+
}

0 commit comments

Comments
 (0)