Skip to content

Commit d8f948d

Browse files
committed
Fix purchases bug and make finalize called only by owner
1 parent 4f9e821 commit d8f948d

File tree

4 files changed

+83
-16
lines changed

4 files changed

+83
-16
lines changed

contracts/LifCrowdsale.sol

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ contract LifCrowdsale is Ownable, Pausable {
229229

230230
// store wei amount in case of TGE min cap not reached
231231
weiRaised = weiRaised.add(weiAmount);
232-
purchases[beneficiary] = weiAmount;
232+
purchases[beneficiary] = purchases[beneficiary].add(weiAmount);
233233
tokensSold = tokensSold.add(tokens);
234234

235235
token.mint(beneficiary, tokens);
@@ -273,8 +273,11 @@ contract LifCrowdsale is Ownable, Pausable {
273273
// calculate the max amount of wei for the foundation
274274
uint256 foundationBalanceCapWei = maxFoundationCapUSD.mul(weiPerUSDinTGE);
275275

276-
// if the minimiun cap for the MVM is not reached transfer all funds to foundation
277-
// else if the min cap for the MVM is reached, create it and send the remaining funds
276+
// If the minimiun cap for the MVM is not reached transfer all funds to foundation
277+
// else if the min cap for the MVM is reached, create it and send the remaining funds.
278+
// We use weiRaised to compare becuase that is the total amount of wei raised in all TGE
279+
// but we have to distribute the balance using `this.balance` because thats the amount
280+
// raised by the crowdsale
278281
if (weiRaised <= foundationBalanceCapWei) {
279282

280283
foundationWallet.transfer(this.balance);
@@ -398,6 +401,12 @@ contract LifCrowdsale is Ownable, Pausable {
398401
uint256 toReturn = purchases[contributor];
399402
assert(toReturn > 0);
400403

404+
uint256 tokenBalance = token.balanceOf(contributor);
405+
406+
// Substract weiRaised and tokens sold
407+
weiRaised = weiRaised.sub(toReturn);
408+
tokensSold = tokensSold.sub(tokenBalance);
409+
token.burn(contributor, tokenBalance);
401410
purchases[contributor] = 0;
402411

403412
contributor.transfer(toReturn);
@@ -409,7 +418,7 @@ contract LifCrowdsale is Ownable, Pausable {
409418
Mechanism in case the soft cap was exceeded. It also unpauses the token to
410419
enable transfers. It can be called only once, after `end2Timestamp`
411420
*/
412-
function finalize() public whenNotPaused hasEnded {
421+
function finalize() public onlyOwner hasEnded {
413422
require(!isFinalized);
414423

415424
// foward founds and unpause token only if minCap is reached

contracts/LifToken.sol

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ pragma solidity ^0.4.18;
33
import "zeppelin-solidity/contracts/token/ERC827/ERC827Token.sol";
44
import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
55
import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol";
6-
import "zeppelin-solidity/contracts/token/ERC20/BurnableToken.sol";
76
import "zeppelin-solidity/contracts/token/ERC20/PausableToken.sol";
87

98
/**
@@ -12,9 +11,9 @@ import "zeppelin-solidity/contracts/token/ERC20/PausableToken.sol";
1211
Implementation of Líf, the ERC827 token for Winding Tree, an extension of the
1312
ERC20 token with extra methods to transfer value and data to execute a call
1413
on transfer.
15-
Uses OpenZeppelin StandardToken, ERC827Token, BurnableToken, MintableToken and PausableToken.
14+
Uses OpenZeppelin StandardToken, ERC827Token, MintableToken and PausableToken.
1615
*/
17-
contract LifToken is StandardToken, ERC827Token, BurnableToken, MintableToken, PausableToken {
16+
contract LifToken is StandardToken, ERC827Token, MintableToken, PausableToken {
1817
// Token Name
1918
string public constant NAME = "Líf";
2019

@@ -30,11 +29,34 @@ contract LifToken is StandardToken, ERC827Token, BurnableToken, MintableToken, P
3029
* @param _value The amount of tokens to be burned.
3130
*/
3231
function burn(uint256 _value) public whenNotPaused {
33-
super.burn(_value);
32+
33+
require(_value <= balances[msg.sender]);
34+
35+
balances[msg.sender] = balances[msg.sender].sub(_value);
36+
totalSupply_ = totalSupply_.sub(_value);
3437

3538
// a Transfer event to 0x0 can be useful for observers to keep track of
3639
// all the Lif by just looking at those events
3740
Transfer(msg.sender, address(0), _value);
3841
}
3942

43+
/**
44+
* @dev Burns a specific amount of tokens of an address
45+
* This function can be called only by the owner in the minting process
46+
*
47+
* @param _value The amount of tokens to be burned.
48+
*/
49+
function burn(address burner, uint256 _value) public onlyOwner {
50+
51+
require(!mintingFinished);
52+
53+
require(_value <= balances[burner]);
54+
55+
balances[burner] = balances[burner].sub(_value);
56+
totalSupply_ = totalSupply_.sub(_value);
57+
58+
// a Transfer event to 0x0 can be useful for observers to keep track of
59+
// all the Lif by just looking at those events
60+
Transfer(burner, address(0), _value);
61+
}
4062
}

test/CrowdsaleGenTest.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,33 @@ contract('LifCrowdsale Property-based test', function (accounts) {
346346
{ 'type': 'waitTime', 'seconds': duration.days(1) },
347347
{ 'type': 'buyTokens', beneficiary: 3, account: 4, eth: 23000 },
348348
{ 'type': 'waitTime', 'seconds': duration.days(1) },
349-
{ 'type': 'finalizeCrowdsale', fromAccount: 5 },
349+
{ 'type': 'finalizeCrowdsale', fromAccount: 3 },
350+
],
351+
crowdsale: {
352+
rate1: 10,
353+
rate2: 9,
354+
setWeiLockSeconds: 3600,
355+
foundationWallet: 2,
356+
foundersWallet: 2,
357+
owner: 3,
358+
},
359+
};
360+
361+
await runGeneratedCrowdsaleAndCommands(crowdsaleAndCommands);
362+
});
363+
364+
it('Execute a normal TGE and ends with 0 funds', async function () {
365+
let crowdsaleAndCommands = {
366+
commands: [
367+
{ 'type': 'checkRate' },
368+
{ 'type': 'setWeiPerUSDinTGE', wei: 1500000000000000, fromAccount: 3 },
369+
{ 'type': 'waitTime', 'seconds': duration.days(1) },
370+
{ 'type': 'buyTokens', beneficiary: 3, account: 4, eth: 40000 },
371+
{ 'type': 'waitTime', 'seconds': duration.days(1) },
372+
{ 'type': 'buyTokens', beneficiary: 3, account: 4, eth: 23000 },
373+
{ 'type': 'waitTime', 'seconds': duration.days(1) },
374+
{ 'type': 'returnPurchase', fromAccount: 3, contributor: 3 },
375+
{ 'type': 'finalizeCrowdsale', fromAccount: 3 },
350376
],
351377
crowdsale: {
352378
rate1: 10,
@@ -372,7 +398,7 @@ contract('LifCrowdsale Property-based test', function (accounts) {
372398
{ 'type': 'waitTime', 'seconds': duration.days(1.1) },
373399
{ 'type': 'buyTokens', beneficiary: 3, account: 4, eth: 60000 },
374400
{ 'type': 'waitTime', 'seconds': duration.days(2) },
375-
{ 'type': 'finalizeCrowdsale', fromAccount: 5 },
401+
{ 'type': 'finalizeCrowdsale', fromAccount: 3 },
376402
{ 'type': 'pauseToken', 'pause': false, 'fromAccount': 3 },
377403
],
378404
crowdsale: {
@@ -625,7 +651,7 @@ contract('LifCrowdsale Property-based test', function (accounts) {
625651
await runGeneratedCrowdsaleAndCommands({
626652
'commands': [
627653
{ 'type': 'fundCrowdsaleOverSoftCap', 'account': 8, 'softCapExcessWei': 15, 'finalize': true },
628-
{ 'type': 'returnPurchase', 'eth': 1, 'fromAccount': 0, 'contributor': 8 },
654+
{ 'type': 'returnPurchase', 'fromAccount': 0, 'contributor': 8 },
629655
{ 'type': 'pauseToken', 'pause': false, 'fromAccount': 2 },
630656
{ 'type': 'transfer', 'lif': 10, 'fromAccount': 8, 'toAccount': 2 },
631657
],
@@ -644,7 +670,7 @@ contract('LifCrowdsale Property-based test', function (accounts) {
644670
await runGeneratedCrowdsaleAndCommands({
645671
'commands': [
646672
{ 'type': 'fundCrowdsaleOverSoftCap', 'account': 8, 'softCapExcessWei': 15, 'finalize': true },
647-
{ 'type': 'returnPurchase', 'eth': 33, 'fromAccount': 0, 'contributor': 8 },
673+
{ 'type': 'returnPurchase', 'fromAccount': 0, 'contributor': 8 },
648674
],
649675
'crowdsale': {
650676
'rate1': 23,

test/commands.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ async function runFinalizeCrowdsaleCommand (command, state) {
275275

276276
let shouldThrow = state.crowdsaleFinalized ||
277277
state.crowdsalePaused || (state.weiPerUSDinTGE === 0) ||
278+
(account !== gen.getAccount(state.owner)) ||
278279
hasZeroAddress ||
279280
(nextTimestamp <= state.crowdsaleData.end2Timestamp);
280281

@@ -442,7 +443,7 @@ async function runClaimEthCommand (command, state) {
442443
async function runReturnPurchaseCommand (command, state) {
443444
let account = gen.getAccount(command.fromAccount),
444445
contributor = gen.getAccount(command.contributor),
445-
purchases = _.filter(state.purchases, (p) => p.account === command.contributor),
446+
purchases = _.filter(state.purchases, (p) => p.beneficiary === command.contributor),
446447
hasZeroAddress = (isZeroAddress(contributor) || isZeroAddress(account)),
447448
nextTimestamp = latestTime();
448449

@@ -462,6 +463,15 @@ async function runReturnPurchaseCommand (command, state) {
462463
_.map(purchases, (p) => p.wei),
463464
(accum, wei) => accum.plus(wei)
464465
);
466+
467+
state.purchases = _.remove(state.purchases, function (p) {
468+
return p.beneficiary !== command.contributor;
469+
});
470+
471+
state.weiRaised = state.weiRaised.sub(returnedAmount);
472+
state.totalSupply = state.totalSupply.sub(state.balances[command.contributor]);
473+
state.balances[command.contributor] = 0;
474+
465475
state = increaseEthBalance(state, command.contributor, returnedAmount);
466476
state = decreaseEthBalance(state, command.fromAccount, help.txGasCost(tx));
467477
} catch (e) {
@@ -614,7 +624,7 @@ async function runFundCrowdsaleBelowMinCap (command, state) {
614624
await increaseTimeTestRPCTo(state.crowdsaleData.end2Timestamp + 1);
615625
}
616626

617-
state = await runFinalizeCrowdsaleCommand({ fromAccount: command.account }, state);
627+
state = await runFinalizeCrowdsaleCommand({ fromAccount: state.owner }, state);
618628

619629
// verify that the crowdsale is finalized and funded
620630
assert.equal(true, state.crowdsaleFinalized);
@@ -650,7 +660,7 @@ async function runFundCrowdsaleBelowSoftCap (command, state) {
650660
await increaseTimeTestRPCTo(state.crowdsaleData.end2Timestamp + 1);
651661
}
652662

653-
state = await runFinalizeCrowdsaleCommand({ fromAccount: command.account }, state);
663+
state = await runFinalizeCrowdsaleCommand({ fromAccount: state.owner }, state);
654664

655665
// verify that the crowdsale is finalized and funded
656666
assert.equal(true, state.crowdsaleFinalized);
@@ -699,7 +709,7 @@ async function runFundCrowdsaleOverSoftCap (command, state) {
699709
await increaseTimeTestRPCTo(state.crowdsaleData.end2Timestamp + 1);
700710
}
701711

702-
state = await runFinalizeCrowdsaleCommand({ fromAccount: command.account }, state);
712+
state = await runFinalizeCrowdsaleCommand({ fromAccount: state.owner }, state);
703713

704714
// verify that the crowdsale is finalized and funded, but there's no MVM
705715
assert.equal(true, state.crowdsaleFinalized);

0 commit comments

Comments
 (0)