Skip to content
This repository was archived by the owner on Jan 18, 2023. It is now read-only.

Commit 136087a

Browse files
committed
Write log checking and update redeem excluded to handle multiple redemptions
1 parent 452df3c commit 136087a

File tree

3 files changed

+206
-35
lines changed

3 files changed

+206
-35
lines changed

contracts/SetToken.sol

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ contract SetToken is StandardToken, DetailedERC20("", "", 18), Set {
3131

3232
event LogPartialRedemption(
3333
address indexed _sender,
34-
uint indexed _quantity
34+
uint indexed _quantity,
35+
address[] _excludedComponents
36+
);
37+
38+
event LogRedeemExcluded(
39+
address indexed _sender,
40+
uint[] _quantities,
41+
address[] _component
3542
);
3643

3744
modifier hasSufficientBalance(uint quantity) {
@@ -210,23 +217,34 @@ contract SetToken is StandardToken, DetailedERC20("", "", 18), Set {
210217
unredeemedComponents[currentExcludedToUnredeem][msg.sender].isRedeemed = false;
211218
}
212219

213-
LogPartialRedemption(msg.sender, quantity);
220+
LogPartialRedemption(msg.sender, quantity, excludedComponents);
214221

215222
return true;
216223
}
217224

218-
function redeemExcluded(uint quantity, address excludedComponent)
225+
function redeemExcluded(uint[] quantities, address[] excludedComponents)
219226
public
220227
returns (bool success)
221228
{
222-
// Check there is enough balance
223-
uint remainingBalance = unredeemedComponents[excludedComponent][msg.sender].balance;
224-
require(remainingBalance >= quantity);
229+
require(quantities.length > 0);
230+
require(excludedComponents.length > 0);
231+
require(quantities.length == excludedComponents.length);
225232

226-
// To prevent re-entrancy attacks, decrement the user's Set balance
227-
unredeemedComponents[excludedComponent][msg.sender].balance = remainingBalance.sub(quantity);
233+
for (uint i = 0; i < quantities.length; i++) {
234+
address currentComponent = excludedComponents[i];
235+
uint currentQuantity = quantities[i];
236+
237+
// Check there is enough balance
238+
uint remainingBalance = unredeemedComponents[currentComponent][msg.sender].balance;
239+
require(remainingBalance >= currentQuantity);
240+
241+
// To prevent re-entrancy attacks, decrement the user's Set balance
242+
unredeemedComponents[currentComponent][msg.sender].balance = remainingBalance.sub(currentQuantity);
243+
244+
assert(ERC20(currentComponent).transfer(msg.sender, currentQuantity));
245+
}
228246

229-
assert(ERC20(excludedComponent).transfer(msg.sender, quantity));
247+
LogRedeemExcluded(msg.sender, quantities, excludedComponents);
230248

231249
return true;
232250
}

test/logs/SetToken.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,38 @@ export function LogIssuance(
2525
};
2626
}
2727

28+
export function LogRedemption(
29+
senderAddress: Address,
30+
quantity: BigNumber,
31+
setTokenAddress: Address,
32+
): LogInterface {
33+
return {
34+
event: "LogRedemption",
35+
address: setTokenAddress,
36+
args: {
37+
_sender: senderAddress,
38+
_quantity: quantity,
39+
},
40+
};
41+
}
42+
43+
export function LogPartialRedemption(
44+
senderAddress: Address,
45+
quantity: BigNumber,
46+
setTokenAddress: Address,
47+
excludedComponents: Address[],
48+
): LogInterface {
49+
return {
50+
event: "LogPartialRedemption",
51+
address: setTokenAddress,
52+
args: {
53+
_sender: senderAddress,
54+
_quantity: quantity,
55+
_excludedComponents: excludedComponents,
56+
},
57+
};
58+
}
59+
2860
export function LogTransfer(
2961
from: Address,
3062
to: Address,
@@ -69,3 +101,63 @@ export function getExpectedIssueLogs(
69101

70102
return result;
71103
}
104+
105+
export function getExpectedRedeemLogs(
106+
componentAddresses: Address[],
107+
quantityTransferred: BigNumber[],
108+
setTokenAddress: Address,
109+
quantityRedeemed: BigNumber,
110+
sender: Address,
111+
): LogInterface[] {
112+
const result: LogInterface[] = [];
113+
// Create transfer logs from components and units
114+
_.each(componentAddresses, (componentAddress, index) => {
115+
result.push(LogTransfer(
116+
setTokenAddress,
117+
sender,
118+
quantityTransferred[index],
119+
componentAddresses[index],
120+
));
121+
});
122+
123+
// Create Redemption Log
124+
result.push(LogRedemption(
125+
sender,
126+
quantityRedeemed,
127+
setTokenAddress,
128+
));
129+
130+
return result;
131+
}
132+
133+
export function getExpectedPartialRedeemLogs(
134+
componentAddresses: Address[],
135+
excludedComponents: Address[],
136+
quantityTransferred: BigNumber[],
137+
setTokenAddress: Address,
138+
quantityRedeemed: BigNumber,
139+
sender: Address,
140+
): LogInterface[] {
141+
const result: LogInterface[] = [];
142+
// Create transfer logs from transferred components and units
143+
_.each(componentAddresses, (componentAddress, index) => {
144+
if (_.indexOf(excludedComponents, componentAddress) < 0) {
145+
result.push(LogTransfer(
146+
setTokenAddress,
147+
sender,
148+
quantityTransferred[index],
149+
componentAddresses[index],
150+
));
151+
}
152+
});
153+
154+
// Create Partial Redemption Log
155+
result.push(LogPartialRedemption(
156+
sender,
157+
quantityRedeemed,
158+
setTokenAddress,
159+
excludedComponents,
160+
));
161+
162+
return result;
163+
}

test/setToken-base.spec.ts

Lines changed: 87 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ const { expect, assert } = chai;
2424

2525
import { extractLogEventAndArgs } from "./logs/log_utils";
2626

27-
import { getExpectedIssueLogs } from "./logs/SetToken";
27+
import {
28+
getExpectedIssueLogs,
29+
getExpectedRedeemLogs,
30+
getExpectedPartialRedeemLogs,
31+
} from "./logs/SetToken";
2832

2933
import {
3034
assertTokenBalance,
@@ -246,19 +250,21 @@ contract("{Set}", (accounts) => {
246250
// This is about ~2M Gas.
247251
describe("of 60 Component Set", () => {
248252
it(`should work`, async () => {
249-
await deployStandardSetAndApprove(5);
253+
await deployStandardSetAndApprove(60);
254+
255+
quantitiesToTransfer = _.map(units, (unit) => unit.mul(standardQuantityIssued).div(gWei(1)));
250256

251257
const issuanceReceipt = await setToken.issue(standardQuantityIssued, TX_DEFAULTS);
252-
// const { logs } = issuanceReceipt;
253-
// const formattedLogs = _.map(logs, (log) => extractLogEventAndArgs(log));
254-
// const expectedLogs = getExpectedIssueLogs(
255-
// componentAddresses,
256-
// quantitiesToTransfer,
257-
// setToken.address,
258-
// standardQuantityIssued,
259-
// testAccount,
260-
// );
261-
// expect(JSON.stringify(formattedLogs)).to.equal(JSON.stringify(expectedLogs));
258+
const { logs } = issuanceReceipt;
259+
const formattedLogs = _.map(logs, (log) => extractLogEventAndArgs(log));
260+
const expectedLogs = getExpectedIssueLogs(
261+
componentAddresses,
262+
quantitiesToTransfer,
263+
setToken.address,
264+
standardQuantityIssued,
265+
testAccount,
266+
);
267+
expect(JSON.stringify(formattedLogs)).to.equal(JSON.stringify(expectedLogs));
262268
assertTokenBalance(setToken, standardQuantityIssued, testAccount);
263269
});
264270
});
@@ -274,7 +280,18 @@ contract("{Set}", (accounts) => {
274280
// Quantity A expected to be deduced, which is 1/2 of an A token
275281
const quantity1 = standardQuantityIssued.mul(thousandthGwei).div(gWei(1));
276282

277-
await setToken.issue(standardQuantityIssued, TX_DEFAULTS);
283+
const issuanceReceipt = await setToken.issue(standardQuantityIssued, TX_DEFAULTS);
284+
const { logs } = issuanceReceipt;
285+
const formattedLogs = _.map(logs, (log) => extractLogEventAndArgs(log));
286+
const expectedLogs = getExpectedIssueLogs(
287+
componentAddresses,
288+
[quantity1],
289+
setToken.address,
290+
standardQuantityIssued,
291+
testAccount,
292+
);
293+
294+
expect(JSON.stringify(formattedLogs)).to.equal(JSON.stringify(expectedLogs));
278295

279296
assertTokenBalance(components[0], initialTokens.sub(quantity1), testAccount);
280297
assertTokenBalance(setToken, standardQuantityIssued, testAccount);
@@ -311,13 +328,17 @@ contract("{Set}", (accounts) => {
311328

312329
it(`should work`, async () => {
313330
const redeemReceipt = await setToken.redeem(standardQuantityIssued, TX_DEFAULTS);
314-
const redeemLog = redeemReceipt.logs[redeemReceipt.logs.length - 1].args;
315-
316-
// The logs should have the right sender
317-
assert.strictEqual(redeemLog._sender, testAccount);
331+
const { logs } = redeemReceipt;
332+
const formattedLogs = _.map(logs, (log) => extractLogEventAndArgs(log));
333+
const expectedLogs = getExpectedRedeemLogs(
334+
componentAddresses,
335+
quantitiesToTransfer,
336+
setToken.address,
337+
standardQuantityIssued,
338+
testAccount,
339+
);
318340

319-
// The logs should have the right quantity
320-
expect(redeemLog._quantity).to.be.bignumber.equal(standardQuantityIssued);
341+
expect(JSON.stringify(formattedLogs)).to.equal(JSON.stringify(expectedLogs));
321342

322343
const [component1, component2] = components;
323344
const [units1, units2] = units;
@@ -341,7 +362,18 @@ contract("{Set}", (accounts) => {
341362
it(`should work`, async () => {
342363
await deployStandardSetAndIssue(60, standardQuantityIssued);
343364

344-
await setToken.redeem(standardQuantityIssued, TX_DEFAULTS);
365+
const redeemReceipt = await setToken.redeem(standardQuantityIssued, TX_DEFAULTS);
366+
const { logs } = redeemReceipt;
367+
const formattedLogs = _.map(logs, (log) => extractLogEventAndArgs(log));
368+
const expectedLogs = getExpectedRedeemLogs(
369+
componentAddresses,
370+
quantitiesToTransfer,
371+
setToken.address,
372+
standardQuantityIssued,
373+
testAccount,
374+
);
375+
376+
expect(JSON.stringify(formattedLogs)).to.equal(JSON.stringify(expectedLogs));
345377
assertTokenBalance(setToken, new BigNumber(0), testAccount);
346378
});
347379
});
@@ -353,7 +385,19 @@ contract("{Set}", (accounts) => {
353385
});
354386

355387
it("should work", async () => {
356-
await setToken.redeem(standardQuantityIssued, TX_DEFAULTS);
388+
const redeemReceipt = await setToken.redeem(standardQuantityIssued, TX_DEFAULTS);
389+
const { logs } = redeemReceipt;
390+
const formattedLogs = _.map(logs, (log) => extractLogEventAndArgs(log));
391+
const expectedLogs = getExpectedRedeemLogs(
392+
componentAddresses,
393+
quantitiesToTransfer,
394+
setToken.address,
395+
standardQuantityIssued,
396+
testAccount,
397+
);
398+
399+
expect(JSON.stringify(formattedLogs)).to.equal(JSON.stringify(expectedLogs));
400+
357401
const [component1] = components;
358402
const [units1] = units;
359403

@@ -382,7 +426,24 @@ contract("{Set}", (accounts) => {
382426
const [units1, units2, units3] = units;
383427
const [quantity1, quantity2, quantity3] = quantitiesToTransfer;
384428

385-
await setToken.partialRedeem(standardQuantityIssued, [componentToExclude], TX_DEFAULTS);
429+
const partialRedeemReceipt = await setToken.partialRedeem(
430+
standardQuantityIssued,
431+
[componentToExclude],
432+
TX_DEFAULTS,
433+
);
434+
435+
const { logs } = partialRedeemReceipt;
436+
const formattedLogs = _.map(logs, (log) => extractLogEventAndArgs(log));
437+
const expectedLogs = getExpectedPartialRedeemLogs(
438+
componentAddresses,
439+
[componentToExclude],
440+
quantitiesToTransfer,
441+
setToken.address,
442+
standardQuantityIssued,
443+
testAccount,
444+
);
445+
446+
expect(JSON.stringify(formattedLogs)).to.equal(JSON.stringify(expectedLogs));
386447

387448
assertTokenBalance(setToken, new BigNumber(0), testAccount);
388449
assertTokenBalance(component1, initialTokens.sub(quantity1), testAccount);
@@ -416,7 +477,7 @@ contract("{Set}", (accounts) => {
416477
});
417478
});
418479

419-
describe("Redeem Excluded", async () => {
480+
describe.only("Redeem Excluded", async () => {
420481
let componentExcluded: any;
421482
let componentAddressExcluded: Address;
422483

@@ -430,7 +491,7 @@ contract("{Set}", (accounts) => {
430491
});
431492

432493
it("should work", async () => {
433-
await setToken.redeemExcluded(quantitiesToTransfer[0], componentAddressExcluded, TX_DEFAULTS);
494+
await setToken.redeemExcluded([quantitiesToTransfer[0]], [componentAddressExcluded], TX_DEFAULTS);
434495

435496
assertTokenBalance(componentExcluded, initialTokens, testAccount);
436497

@@ -441,8 +502,8 @@ contract("{Set}", (accounts) => {
441502
it("should fail if the user doesn't have enough balance", async () => {
442503
const largeQuantity = new BigNumber("1000000000000000000000000000000000000");
443504
await expectRevertError(setToken.redeemExcluded(
444-
largeQuantity,
445-
componentAddressExcluded,
505+
[largeQuantity],
506+
[componentAddressExcluded],
446507
TX_DEFAULTS,
447508
));
448509
});

0 commit comments

Comments
 (0)