-
Notifications
You must be signed in to change notification settings - Fork 2
/
Position.sol
259 lines (234 loc) · 11 KB
/
Position.sol
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 { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
/**
* @title Position
* @author Set Protocol
*
* Collection of helper functions for handling and updating SetToken Positions
*
* CHANGELOG:
* - Updated editExternalPosition to work when no external position is associated with module
*/
library Position {
using SafeCast for uint256;
using SafeMath for uint256;
using SafeCast for int256;
using SignedSafeMath for int256;
using PreciseUnitMath for uint256;
/* ============ Helper ============ */
/**
* Returns whether the SetToken has a default position for a given component (if the real unit is > 0)
*/
function hasDefaultPosition(ISetToken _setToken, address _component) internal view returns(bool) {
return _setToken.getDefaultPositionRealUnit(_component) > 0;
}
/**
* Returns whether the SetToken has an external position for a given component (if # of position modules is > 0)
*/
function hasExternalPosition(ISetToken _setToken, address _component) internal view returns(bool) {
return _setToken.getExternalPositionModules(_component).length > 0;
}
/**
* Returns whether the SetToken component default position real unit is greater than or equal to units passed in.
*/
function hasSufficientDefaultUnits(ISetToken _setToken, address _component, uint256 _unit) internal view returns(bool) {
return _setToken.getDefaultPositionRealUnit(_component) >= _unit.toInt256();
}
/**
* Returns whether the SetToken component external position is greater than or equal to the real units passed in.
*/
function hasSufficientExternalUnits(
ISetToken _setToken,
address _component,
address _positionModule,
uint256 _unit
)
internal
view
returns(bool)
{
return _setToken.getExternalPositionRealUnit(_component, _positionModule) >= _unit.toInt256();
}
/**
* If the position does not exist, create a new Position and add to the SetToken. If it already exists,
* then set the position units. If the new units is 0, remove the position. Handles adding/removing of
* components where needed (in light of potential external positions).
*
* @param _setToken Address of SetToken being modified
* @param _component Address of the component
* @param _newUnit Quantity of Position units - must be >= 0
*/
function editDefaultPosition(ISetToken _setToken, address _component, uint256 _newUnit) internal {
bool isPositionFound = hasDefaultPosition(_setToken, _component);
if (!isPositionFound && _newUnit > 0) {
// If there is no Default Position and no External Modules, then component does not exist
if (!hasExternalPosition(_setToken, _component)) {
_setToken.addComponent(_component);
}
} else if (isPositionFound && _newUnit == 0) {
// If there is a Default Position and no external positions, remove the component
if (!hasExternalPosition(_setToken, _component)) {
_setToken.removeComponent(_component);
}
}
_setToken.editDefaultPositionUnit(_component, _newUnit.toInt256());
}
/**
* Update an external position and remove and external positions or components if necessary. The logic flows as follows:
* 1) If component is not already added then add component and external position.
* 2) If component is added but no existing external position using the passed module exists then add the external position.
* 3) If the existing position is being added to then just update the unit and data
* 4) If the position is being closed and no other external positions or default positions are associated with the component
* then untrack the component and remove external position.
* 5) If the position is being closed and other existing positions still exist for the component then just remove the
* external position.
*
* @param _setToken SetToken being updated
* @param _component Component position being updated
* @param _module Module external position is associated with
* @param _newUnit Position units of new external position
* @param _data Arbitrary data associated with the position
*/
function editExternalPosition(
ISetToken _setToken,
address _component,
address _module,
int256 _newUnit,
bytes memory _data
)
internal
{
if (_newUnit != 0) {
if (!_setToken.isComponent(_component)) {
_setToken.addComponent(_component);
_setToken.addExternalPositionModule(_component, _module);
} else if (!_setToken.isExternalPositionModule(_component, _module)) {
_setToken.addExternalPositionModule(_component, _module);
}
_setToken.editExternalPositionUnit(_component, _module, _newUnit);
_setToken.editExternalPositionData(_component, _module, _data);
} else {
require(_data.length == 0, "Passed data must be null");
// If no default or external position remaining then remove component from components array
if (_setToken.getExternalPositionRealUnit(_component, _module) != 0) {
address[] memory positionModules = _setToken.getExternalPositionModules(_component);
if (_setToken.getDefaultPositionRealUnit(_component) == 0 && positionModules.length == 1) {
require(positionModules[0] == _module, "External positions must be 0 to remove component");
_setToken.removeComponent(_component);
}
_setToken.removeExternalPositionModule(_component, _module);
}
}
}
/**
* Get total notional amount of Default position
*
* @param _setTokenSupply Supply of SetToken in precise units (10^18)
* @param _positionUnit Quantity of Position units
*
* @return Total notional amount of units
*/
function getDefaultTotalNotional(uint256 _setTokenSupply, uint256 _positionUnit) internal pure returns (uint256) {
return _setTokenSupply.preciseMul(_positionUnit);
}
/**
* Get position unit from total notional amount
*
* @param _setTokenSupply Supply of SetToken in precise units (10^18)
* @param _totalNotional Total notional amount of component prior to
* @return Default position unit
*/
function getDefaultPositionUnit(uint256 _setTokenSupply, uint256 _totalNotional) internal pure returns (uint256) {
return _totalNotional.preciseDiv(_setTokenSupply);
}
/**
* Get the total tracked balance - total supply * position unit
*
* @param _setToken Address of the SetToken
* @param _component Address of the component
* @return Notional tracked balance
*/
function getDefaultTrackedBalance(ISetToken _setToken, address _component) internal view returns(uint256) {
int256 positionUnit = _setToken.getDefaultPositionRealUnit(_component);
return _setToken.totalSupply().preciseMul(positionUnit.toUint256());
}
/**
* Calculates the new default position unit and performs the edit with the new unit
*
* @param _setToken Address of the SetToken
* @param _component Address of the component
* @param _setTotalSupply Current SetToken supply
* @param _componentPreviousBalance Pre-action component balance
* @return Current component balance
* @return Previous position unit
* @return New position unit
*/
function calculateAndEditDefaultPosition(
ISetToken _setToken,
address _component,
uint256 _setTotalSupply,
uint256 _componentPreviousBalance
)
internal
returns(uint256, uint256, uint256)
{
uint256 currentBalance = IERC20(_component).balanceOf(address(_setToken));
uint256 positionUnit = _setToken.getDefaultPositionRealUnit(_component).toUint256();
uint256 newTokenUnit;
if (currentBalance > 0) {
newTokenUnit = calculateDefaultEditPositionUnit(
_setTotalSupply,
_componentPreviousBalance,
currentBalance,
positionUnit
);
} else {
newTokenUnit = 0;
}
editDefaultPosition(_setToken, _component, newTokenUnit);
return (currentBalance, positionUnit, newTokenUnit);
}
/**
* Calculate the new position unit given total notional values pre and post executing an action that changes SetToken state
* The intention is to make updates to the units without accidentally picking up airdropped assets as well.
*
* @param _setTokenSupply Supply of SetToken in precise units (10^18)
* @param _preTotalNotional Total notional amount of component prior to executing action
* @param _postTotalNotional Total notional amount of component after the executing action
* @param _prePositionUnit Position unit of SetToken prior to executing action
* @return New position unit
*/
function calculateDefaultEditPositionUnit(
uint256 _setTokenSupply,
uint256 _preTotalNotional,
uint256 _postTotalNotional,
uint256 _prePositionUnit
)
internal
pure
returns (uint256)
{
// If pre action total notional amount is greater then subtract post action total notional and calculate new position units
uint256 airdroppedAmount = _preTotalNotional.sub(_prePositionUnit.preciseMul(_setTokenSupply));
return _postTotalNotional.sub(airdroppedAmount).preciseDiv(_setTokenSupply);
}
}