@@ -2,10 +2,10 @@ pragma solidity ^0.4.18;
2
2
3
3
/**
4
4
* @title InteractiveCrowdsaleLib
5
- * @author Majoolr.io
5
+ * @author Modular, Inc
6
6
*
7
7
* version 1.0.0
8
- * Copyright (c) 2017 Modular, LLC
8
+ * Copyright (c) 2017 Modular, Inc
9
9
* The MIT License (MIT)
10
10
*
11
11
* The InteractiveCrowdsale Library provides functionality to create a crowdsale
@@ -63,24 +63,33 @@ library InteractiveCrowdsaleLib {
63
63
64
64
// amount of value committed at this valuation, cannot rely on owner balance
65
65
// due to fluctations in commitment calculations needed after owner withdraws
66
- // in other words, the total amount of ETH committed, including total bids that will eventually get partial purchases
66
+ // in other words, the total amount of ETH committed, including total bids
67
+ // that will eventually get partial purchases
67
68
uint256 valueCommitted;
68
69
69
- // the bucket that sits either at or just below current total valuation. determines where the cutoff point is for bids in the sale
70
+ // the bucket that sits either at or just below current total valuation.
71
+ // determines where the cutoff point is for bids in the sale
70
72
uint256 currentBucket;
71
73
74
+ // the fraction of each minimal valuation bidder's ether refund, 'q' is from the paper
75
+ // and is calculated when finalizing the sale
76
+ uint256 q;
77
+
72
78
// minimim amount that the sale needs to make to be successfull
73
79
uint256 minimumRaise;
74
80
75
81
// percentage of total tokens being sold in this sale
76
82
uint8 percentBeingSold;
77
83
78
- // the bonus amount for early bidders. This is a percentage of the base token price that gets added on the the base token price
79
- // used in getCurrentBonus()
84
+ // the bonus amount for early bidders. This is a percentage of the base token
85
+ // price that gets added on the the base token price used in getCurrentBonus()
80
86
uint256 priceBonusPercent;
81
87
82
88
// Indicates that the owner has finalized the sale and withdrawn Ether
83
- bool ownerHasWithdrawnETH;
89
+ bool isFinalized;
90
+
91
+ // Set to true if the sale is canceled
92
+ bool isCanceled;
84
93
85
94
// shows the price that the address purchased tokens at
86
95
mapping (address => uint256 ) pricePurchasedAt;
@@ -113,15 +122,16 @@ library InteractiveCrowdsaleLib {
113
122
// Indicates when the price of the token changes
114
123
event LogTokenPriceChange (uint256 amount , string Msg );
115
124
116
- // Logs the current bucket that the valuation points to, the total valuation of the sale, and the amount of ETH committed, including total bids that will eventually get partial purchases
125
+ // Logs the current bucket that the valuation points to, the total valuation of
126
+ // the sale, and the amount of ETH committed, including total bids that will eventually get partial purchases
117
127
event BucketAndValuationAndCommitted (uint256 bucket , uint256 valuation , uint256 committed );
118
128
119
129
/// @dev Called by a crowdsale contract upon creation.
120
130
/// @param self Stored crowdsale from crowdsale contract
121
131
/// @param _owner Address of crowdsale owner
122
132
/// @param _saleData Array of 3 item arrays such that, in each 3 element
123
133
/// array index-0 is a timestamp, index-1 is price in tokens/ETH
124
- /// index-2 is address purchase valuation at that time, 0 if no address valuation
134
+ /// index-2 is address purchase cap at that time, 0 if no address cap
125
135
/// @param _priceBonusPercent the bonus amount for early bidders
126
136
/// @param _minimumRaise minimim amount that the sale needs to make to be successfull
127
137
/// @param _endWithdrawalTime timestamp that indicates that manual withdrawals are no longer allowed
@@ -208,18 +218,17 @@ library InteractiveCrowdsaleLib {
208
218
remainder = weiTokens % 1000000000000000000 ;
209
219
remainder = remainder / _price;
210
220
211
- // make sure there are enough tokens available to satisfy the bid
212
- assert (numTokens <= self.base.token.balanceOf (this ));
213
-
214
221
return (numTokens,remainder);
215
222
}
216
223
217
- /// @dev Called when an address wants to submit bid to the sale
224
+ /// @dev Called when an address wants to submit a bid to the sale
218
225
/// @param self Stored crowdsale from crowdsale contract
219
226
/// @return currentBonus percentage of the bonus that is applied for the purchase
220
227
function getCurrentBonus (InteractiveCrowdsaleStorage storage self ) internal view returns (uint256 ){
221
- uint256 bonusTime = self.endWithdrawalTime - self.base.startTime; // can't underflow becuase endWithdrawalTime > startTime
222
- uint256 elapsed = now - self.base.startTime; // can't underflow becuase now > startTime
228
+ // can't underflow becuase endWithdrawalTime > startTime
229
+ uint256 bonusTime = self.endWithdrawalTime - self.base.startTime;
230
+ // can't underflow because now > startTime
231
+ uint256 elapsed = now - self.base.startTime;
223
232
uint256 percentElapsed = (elapsed * 100 )/ bonusTime;
224
233
225
234
bool err;
@@ -244,7 +253,7 @@ library InteractiveCrowdsaleLib {
244
253
require (msg .sender != self.base.owner);
245
254
require (self.base.validPurchase ());
246
255
// bidder can't have already bid
247
- require (self.personalCaps[msg .sender ] == 0 && self.base.hasContributed[msg .sender ] == 0 );
256
+ require (( self.personalCaps[msg .sender ] == 0 ) && ( self.base.hasContributed[msg .sender ] == 0 ) );
248
257
249
258
uint256 _bonusPercent;
250
259
// token purchase bonus only applies before the withdrawal lock
@@ -258,7 +267,8 @@ library InteractiveCrowdsaleLib {
258
267
}
259
268
260
269
// personal valuation and minimum should be set to the proper granularity,
261
- // only three most significant values can be non-zero. reduces the number of possible valuation buckets in the linked list
270
+ // only three most significant values can be non-zero. reduces the number of possible
271
+ // valuation buckets in the linked list
262
272
uint256 digits = numDigits (_personalCap);
263
273
if (digits > 3 ) {
264
274
require ((_personalCap % (10 ** (digits - 3 ))) == 0 );
@@ -376,13 +386,15 @@ library InteractiveCrowdsaleLib {
376
386
//uint256 multiplierPercent = (100*(t - s))/t;
377
387
//self.pricePurchasedAt = pa-((pa-pu)/3)
378
388
379
- uint256 multiplierPercent = (100 * (self.endWithdrawalTime - now ))/ (self.endWithdrawalTime- self.base.startTime);
380
- refundWei = (multiplierPercent* self.base.hasContributed[msg .sender ])/ 100 ;
389
+ uint256 multiplierPercent = (100 * (self.endWithdrawalTime - now )) /
390
+ (self.endWithdrawalTime - self.base.startTime);
391
+ refundWei = (multiplierPercent * self.base.hasContributed[msg .sender ]) / 100 ;
381
392
382
393
self.valuationSums[self.personalCaps[msg .sender ]] -= refundWei;
383
394
self.numBidsAtValuation[self.personalCaps[msg .sender ]] -= 1 ;
384
395
385
- self.pricePurchasedAt[msg .sender ] = self.pricePurchasedAt[msg .sender ]- ((self.pricePurchasedAt[msg .sender ]- self.base.tokensPerEth)/ 3 );
396
+ self.pricePurchasedAt[msg .sender ] = self.pricePurchasedAt[msg .sender ] -
397
+ ((self.pricePurchasedAt[msg .sender ] - self.base.tokensPerEth) / 3 );
386
398
387
399
self.hasManuallyWithdrawn[msg .sender ] = true ;
388
400
@@ -462,26 +474,45 @@ library InteractiveCrowdsaleLib {
462
474
/// @param self stored crowdsale from crowdsale contract
463
475
function finalizeSale (InteractiveCrowdsaleStorage storage self ) public returns (bool ) {
464
476
require (now >= self.base.endTime);
465
- require (! self.ownerHasWithdrawnETH); // can only be called once
477
+ require (! self.isFinalized); // can only be called once
478
+ require (setCanceled (self));
466
479
480
+ self.isFinalized = true ;
467
481
require (launchToken (self));
468
-
469
- self.ownerHasWithdrawnETH = true ;
470
- self.base.ownerBalance = self.totalValuation; // sets ETH raised in the sale to be ready for withdrawal
482
+ // may need to be computed due to EVM rounding errors
483
+ uint256 computedValue;
484
+
485
+ if (! self.isCanceled){
486
+ if (self.totalValuation == self.currentBucket){
487
+ // calculate the fraction of each minimal valuation bidders ether and tokens to refund
488
+ self.q = (100 * (self.valueCommitted - self.totalValuation)/ (self.valuationSums[self.totalValuation])) + 1 ;
489
+ computedValue = self.valueCommitted - self.valuationSums[self.totalValuation];
490
+ computedValue += (self.q * self.valuationSums[self.totalValuation])/ 100 ;
491
+ } else {
492
+ // no computation necessary
493
+ computedValue = self.totalValuation;
494
+ }
495
+ self.base.ownerBalance = computedValue; // sets ETH raised in the sale to be ready for withdrawal
496
+ }
471
497
}
472
498
473
499
/// @dev Mints the token being sold by taking the percentage of the token supply
474
500
/// being sold in this sale along with the valuation, derives all necessary
475
501
/// values and then transfers owner tokens to the owner.
476
502
/// @param self Stored crowdsale from crowdsale contract
477
503
function launchToken (InteractiveCrowdsaleStorage storage self ) internal returns (bool ) {
478
-
479
- uint256 _fullValue = (self.totalValuation* 100 )/ uint256 (self.percentBeingSold); // total valuation of all the tokens not including the bonus
480
- uint256 _bonusValue = ((self.totalValuation * (100 + self.priceBonusPercent))/ 100 ) - self.totalValuation; // total valuation of bonus tokens
481
- uint256 _supply = (_fullValue * self.base.tokensPerEth)/ 1000000000000000000 ; // total supply of all tokens not including the bonus
482
- uint256 _bonusTokens = (_bonusValue * self.base.tokensPerEth)/ 1000000000000000000 ; // total number of bonus tokens
483
- uint256 _ownerTokens = _supply - ((_supply * uint256 (self.percentBeingSold))/ 100 ); // tokens allocated to the owner of the sale
484
- uint256 _totalSupply = _supply + _bonusTokens; // total supply of tokens not including the bonus tokens
504
+ // total valuation of all the tokens not including the bonus
505
+ uint256 _fullValue = (self.totalValuation* 100 )/ uint256 (self.percentBeingSold);
506
+ // total valuation of bonus tokens
507
+ uint256 _bonusValue = ((self.totalValuation * (100 + self.priceBonusPercent))/ 100 ) - self.totalValuation;
508
+ // total supply of all tokens not including the bonus
509
+ uint256 _supply = (_fullValue * self.base.tokensPerEth)/ 1000000000000000000 ;
510
+ // total number of bonus tokens
511
+ uint256 _bonusTokens = (_bonusValue * self.base.tokensPerEth)/ 1000000000000000000 ;
512
+ // tokens allocated to the owner of the sale
513
+ uint256 _ownerTokens = _supply - ((_supply * uint256 (self.percentBeingSold))/ 100 );
514
+ // total supply of tokens not including the bonus tokens
515
+ uint256 _totalSupply = _supply + _bonusTokens;
485
516
486
517
// deploy new token contract with total number of tokens
487
518
self.base.token = new CrowdsaleToken (address (this ),
@@ -492,11 +523,11 @@ library InteractiveCrowdsaleLib {
492
523
self.tokenInfo.stillMinting);
493
524
494
525
// if the sale got canceled, then all the tokens go to the owner and bonus tokens are burned
495
- if (saleCanceled (self)){
526
+ if (! self.isCanceled){
527
+ self.base.token.transfer (self.base.owner, _ownerTokens);
528
+ } else {
496
529
self.base.token.transfer (self.base.owner, _supply);
497
530
self.base.token.burnToken (_bonusTokens);
498
- } else {
499
- self.base.token.transfer (self.base.owner, _ownerTokens);
500
531
}
501
532
// the owner of the crowdsale becomes the new owner of the token contract
502
533
self.base.token.changeOwner (self.base.owner);
@@ -505,13 +536,17 @@ library InteractiveCrowdsaleLib {
505
536
return true ;
506
537
}
507
538
508
- /// @dev returns a boolean indicating if the sale is canceled.
509
- /// This can either be if the minimum raise hasn't been met
539
+ /// @dev returns a boolean indicating if the sale is canceled.
540
+ /// This can either be if the minimum raise hasn't been met
510
541
/// or if it is 30 days after the sale and the owner hasn't finalized the sale.
511
542
/// @return bool canceled indicating if the sale is canceled or not
512
- function saleCanceled (InteractiveCrowdsaleStorage storage self ) internal view returns (bool canceled ){
513
- canceled = (self.totalValuation < self.minimumRaise) ||
514
- ((now > (self.base.endTime + 30 days)) && ! self.ownerHasWithdrawnETH);
543
+ function setCanceled (InteractiveCrowdsaleStorage storage self ) internal returns (bool ){
544
+ bool canceled = (self.totalValuation < self.minimumRaise) ||
545
+ ((now > (self.base.endTime + 30 days)) && ! self.isFinalized);
546
+
547
+ if (canceled) {self.isCanceled = true ;}
548
+
549
+ return true ;
515
550
}
516
551
517
552
/// @dev If the address' personal cap is below the pointer, refund them all their ETH.
@@ -521,16 +556,20 @@ library InteractiveCrowdsaleLib {
521
556
function retreiveFinalResult (InteractiveCrowdsaleStorage storage self ) public returns (bool ) {
522
557
require (now > self.base.endTime);
523
558
require (self.personalCaps[msg .sender ] > 0 );
524
- require (self.ownerHasWithdrawnETH);
525
559
526
560
uint256 numTokens;
527
561
uint256 remainder;
528
562
529
- if (saleCanceled (self)) {
563
+ if (! self.isFinalized){
564
+ require (setCanceled (self));
565
+ require (self.isCanceled);
566
+ }
567
+
568
+ if (self.isCanceled) {
530
569
// if the sale was canceled, everyone gets a full refund
531
570
self.base.leftoverWei[msg .sender ] += self.base.hasContributed[msg .sender ];
532
571
self.base.hasContributed[msg .sender ] = 0 ;
533
- LogErrorMsg (self.totalValuation, "totalValuation has not reached the minimumRaise. All bids have been refunded! " );
572
+ LogErrorMsg (self.totalValuation, "Sale is canceled, all bids have been refunded! " );
534
573
return true ;
535
574
}
536
575
@@ -545,13 +584,9 @@ library InteractiveCrowdsaleLib {
545
584
return self.base.withdrawLeftoverWei ();
546
585
547
586
} else if (self.personalCaps[msg .sender ] == self.totalValuation) {
548
- uint256 q;
549
-
550
- // calculate the fraction of each minimal valuation bidders ether and tokens to refund
551
- q = (100 * (self.valueCommitted - self.totalValuation)/ (self.valuationSums[self.totalValuation])) + 1 ;
552
587
553
588
// calculate the portion that this address has to take out of their bid
554
- uint256 refundAmount = (q* self.base.hasContributed[msg .sender ])/ 100 ;
589
+ uint256 refundAmount = (self. q* self.base.hasContributed[msg .sender ])/ 100 ;
555
590
556
591
// refund that amount of wei to the address
557
592
self.base.leftoverWei[msg .sender ] += refundAmount;
@@ -564,7 +599,9 @@ library InteractiveCrowdsaleLib {
564
599
LogErrorMsg (self.pricePurchasedAt[msg .sender ],"price " );
565
600
566
601
// calculate the number of tokens that the bidder purchased
567
- (numTokens, remainder) = calculateTokenPurchase (self,self.base.hasContributed[msg .sender ],self.pricePurchasedAt[msg .sender ]);
602
+ (numTokens, remainder) = calculateTokenPurchase (self,
603
+ self.base.hasContributed[msg .sender ],
604
+ self.pricePurchasedAt[msg .sender ]);
568
605
569
606
// add tokens to the bidders purchase. can't overflow because it will be under the cap
570
607
self.base.withdrawTokensMap[msg .sender ] += numTokens;
@@ -591,11 +628,6 @@ library InteractiveCrowdsaleLib {
591
628
592
629
/*Functions "inherited" from CrowdsaleLib library*/
593
630
594
- function withdrawTokens (InteractiveCrowdsaleStorage storage self ) internal returns (bool ) {
595
-
596
- return self.base.withdrawTokens ();
597
- }
598
-
599
631
function withdrawLeftoverWei (InteractiveCrowdsaleStorage storage self ) internal returns (bool ) {
600
632
601
633
return self.base.withdrawLeftoverWei ();
@@ -618,10 +650,6 @@ library InteractiveCrowdsaleLib {
618
650
return self.personalCaps[_bidder];
619
651
}
620
652
621
- function getSaleData (InteractiveCrowdsaleStorage storage self , uint256 _timestamp ) internal view returns (uint256 [3 ]) {
622
- return self.base.getSaleData (_timestamp);
623
- }
624
-
625
653
function getTokensSold (InteractiveCrowdsaleStorage storage self ) internal view returns (uint256 ) {
626
654
return self.base.getTokensSold ();
627
655
}
0 commit comments