@@ -19,6 +19,38 @@ contract SetToken is StandardToken, DetailedERC20("", "", 18), Set {
19
19
uint256 public totalSupply;
20
20
address [] public components;
21
21
uint [] public units;
22
+ mapping (address => bool ) internal isComponent;
23
+
24
+
25
+ struct PartialRedeemStatus {
26
+ uint unredeemedBalance;
27
+ bool isRedeemed;
28
+ }
29
+
30
+ // Mapping of token address -> user address -> partialRedeemStatus
31
+ mapping (address => mapping (address => PartialRedeemStatus)) public unredeemedComponents;
32
+
33
+ event LogPartialRedemption (
34
+ address indexed _sender ,
35
+ uint indexed _quantity
36
+ );
37
+
38
+ modifier hasSufficientBalance (uint quantity ) {
39
+ // Check that the sender has sufficient components
40
+ // Since the component length is defined ahead of time, this is not
41
+ // an unbounded loop
42
+ require (balances[msg .sender ] >= quantity);
43
+ _;
44
+ }
45
+
46
+ modifier preventRedeemReEntrancy (address sender , uint quantity ) {
47
+ // To prevent re-entrancy attacks, decrement the user's Set balance
48
+ balances[sender] = balances[sender].sub (quantity);
49
+
50
+ // Decrement the total token supply
51
+ totalSupply = totalSupply.sub (quantity);
52
+ _;
53
+ }
22
54
23
55
/**
24
56
* @dev Constructor Function for the issuance of an {Set} token
@@ -43,6 +75,9 @@ contract SetToken is StandardToken, DetailedERC20("", "", 18), Set {
43
75
// Check that all addresses are non-zero
44
76
address currentComponent = _components[i];
45
77
require (currentComponent != address (0 ));
78
+
79
+ // add component to isComponent mapping
80
+ isComponent[currentComponent] = true ;
46
81
}
47
82
48
83
// As looping operations are expensive, checking for duplicates will be
@@ -99,20 +134,14 @@ contract SetToken is StandardToken, DetailedERC20("", "", 18), Set {
99
134
*
100
135
* The ERC20 components do not need to be approved to call this function
101
136
*
102
- * @param quantity uint The quantity of components desired to redeem in Wei
137
+ * @param quantity uint The quantity of Sets desired to redeem in Wei
103
138
*/
104
- function redeem (uint quantity ) public returns (bool success ) {
105
- // Check that the sender has sufficient components
106
- // Since the component length is defined ahead of time, this is not
107
- // an unbounded loop
108
- require (balances[msg .sender ] >= quantity);
109
-
110
- // To prevent re-entrancy attacks, decrement the user's Set balance
111
- balances[msg .sender ] = balances[msg .sender ].sub (quantity);
112
-
113
- // Decrement the total token supply
114
- totalSupply = totalSupply.sub (quantity);
115
-
139
+ function redeem (uint quantity )
140
+ public
141
+ hasSufficientBalance (quantity)
142
+ preventRedeemReEntrancy (msg .sender , quantity)
143
+ returns (bool success )
144
+ {
116
145
for (uint i = 0 ; i < components.length ; i++ ) {
117
146
address currentComponent = components[i];
118
147
uint currentUnits = units[i];
@@ -135,6 +164,96 @@ contract SetToken is StandardToken, DetailedERC20("", "", 18), Set {
135
164
return true ;
136
165
}
137
166
167
+ /**
168
+ * @dev Function to withdraw a portion of the component tokens of a Set
169
+ *
170
+ * This function should be used in the event that a component token has been
171
+ * paused for transfer temporarily or permanently. This allows users a
172
+ * method to withdraw tokens in the event that one token has been frozen
173
+ *
174
+ * @param quantity uint The quantity of Sets desired to redeem in Wei
175
+ * @param excludedComponents address[] The list of tokens to exclude
176
+ */
177
+ function partialRedeem (uint quantity , address [] excludedComponents )
178
+ public
179
+ hasSufficientBalance (quantity)
180
+ preventRedeemReEntrancy (msg .sender , quantity)
181
+ returns (bool success )
182
+ {
183
+ // Excluded tokens should be less than the number of components
184
+ // Otherwise, use the normal redeem function
185
+ require (excludedComponents.length < components.length );
186
+ require (excludedComponents.length > 0 );
187
+
188
+ for (uint i = 0 ; i < components.length ; i++ ) {
189
+ bool isExcluded = false ;
190
+
191
+ // Transfer value is defined as the currentUnits (in GWei)
192
+ // multiplied by quantity in Wei divided by the units of gWei.
193
+ // We do this to allow fractional units to be defined
194
+ uint transferValue = units[i].fxpMul (quantity, 10 ** 9 );
195
+
196
+ // Protect against the case that the gWei divisor results in a value that is
197
+ // 0 and the user is able to generate Sets without sending a balance
198
+ assert (transferValue > 0 );
199
+
200
+ // This is unideal to do a doubly nested loop, but the number of excludedComponents
201
+ // should generally be a small number
202
+ for (uint j = 0 ; j < excludedComponents.length ; j++ ) {
203
+ address currentExcluded = excludedComponents[j];
204
+
205
+ // Check that excluded token is indeed a component in this contract
206
+ assert (isComponent[currentExcluded]);
207
+
208
+ // If the token is excluded, add to the user's unredeemed component value
209
+ if (components[i] == currentExcluded) {
210
+ // Ensures there are no duplicates
211
+ bool currentIsRedeemed = unredeemedComponents[components[i]][msg .sender ].isRedeemed;
212
+ assert (currentIsRedeemed == false );
213
+
214
+ unredeemedComponents[components[i]][msg .sender ].unredeemedBalance += transferValue;
215
+
216
+ // Mark redeemed to ensure no duplicates
217
+ unredeemedComponents[components[i]][msg .sender ].isRedeemed = true ;
218
+
219
+ isExcluded = true ;
220
+
221
+ }
222
+ }
223
+
224
+ if (isExcluded == false ) {
225
+ // The transaction will fail if any of the components fail to transfer
226
+ assert (ERC20 (components[i]).transfer (msg .sender , transferValue));
227
+ }
228
+ }
229
+
230
+ // Mark all excluded components not redeemed
231
+ for (uint k = 0 ; k < excludedComponents.length ; k++ ) {
232
+ address currentExcludedToUnredeem = excludedComponents[k];
233
+ unredeemedComponents[currentExcludedToUnredeem][msg .sender ].isRedeemed = false ;
234
+ }
235
+
236
+ LogPartialRedemption (msg .sender , quantity);
237
+
238
+ return true ;
239
+ }
240
+
241
+ function redeemExcluded (uint quantity , address excludedComponent )
242
+ public
243
+ returns (bool success )
244
+ {
245
+ // Check there is enough balance
246
+ uint remainingBalance = unredeemedComponents[excludedComponent][msg .sender ].unredeemedBalance;
247
+ require (remainingBalance >= quantity);
248
+
249
+ // To prevent re-entrancy attacks, decrement the user's Set balance
250
+ unredeemedComponents[excludedComponent][msg .sender ].unredeemedBalance = remainingBalance.sub (quantity);
251
+
252
+ assert (ERC20 (excludedComponent).transfer (msg .sender , quantity));
253
+
254
+ return true ;
255
+ }
256
+
138
257
function componentCount () public view returns (uint componentsLength ) {
139
258
return components.length ;
140
259
}
0 commit comments