Skip to content

Commit 4726245

Browse files
maxsam4adamdossa
authored andcommitted
Ported STO fixes from dev-2.1.0 (#591)
* Ported STO fixes from dev-2.1.0 * Revert when cap reached instead of failing silently * Restored missing require * USDTSTO granularity edge case fixed
1 parent 91b415f commit 4726245

File tree

4 files changed

+186
-53
lines changed

4 files changed

+186
-53
lines changed

contracts/modules/STO/Capped/CappedSTO.sol

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ contract CappedSTO is CappedSTOStorage, STO, ReentrancyGuard {
104104
weiAmount = weiAmount.sub(refund);
105105

106106
_forwardFunds(refund);
107-
_postValidatePurchase(_beneficiary, weiAmount);
108107
}
109108

110109
/**
@@ -116,7 +115,6 @@ contract CappedSTO is CappedSTOStorage, STO, ReentrancyGuard {
116115
require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "Mode of investment is not POLY");
117116
uint256 refund = _processTx(msg.sender, _investedPOLY);
118117
_forwardPoly(msg.sender, wallet, _investedPOLY.sub(refund));
119-
_postValidatePurchase(msg.sender, _investedPOLY.sub(refund));
120118
}
121119

122120
/**
@@ -185,7 +183,6 @@ contract CappedSTO is CappedSTOStorage, STO, ReentrancyGuard {
185183
_processPurchase(_beneficiary, tokens);
186184
emit TokenPurchase(msg.sender, _beneficiary, _investedAmount, tokens);
187185

188-
_updatePurchasingState(_beneficiary, _investedAmount);
189186
}
190187

191188
/**
@@ -197,24 +194,11 @@ contract CappedSTO is CappedSTOStorage, STO, ReentrancyGuard {
197194
function _preValidatePurchase(address _beneficiary, uint256 _investedAmount) internal view {
198195
require(_beneficiary != address(0), "Beneficiary address should not be 0x");
199196
require(_investedAmount != 0, "Amount invested should not be equal to 0");
200-
uint256 tokens;
201-
(tokens, ) = _getTokenAmount(_investedAmount);
202-
require(totalTokensSold.add(tokens) <= cap, "Investment more than cap is not allowed");
197+
require(_canBuy(_beneficiary), "Unauthorized");
203198
/*solium-disable-next-line security/no-block-members*/
204199
require(now >= startTime && now <= endTime, "Offering is closed/Not yet started");
205200
}
206201

207-
/**
208-
* @notice Validation of an executed purchase.
209-
Observe state and use revert statements to undo rollback when valid conditions are not met.
210-
*/
211-
function _postValidatePurchase(
212-
address _beneficiary,
213-
uint256 /*_investedAmount*/
214-
) internal view {
215-
require(_canBuy(_beneficiary), "Unauthorized");
216-
}
217-
218202
/**
219203
* @notice Source of tokens.
220204
Override this method to modify the way in which the crowdsale ultimately gets and sends its tokens.
@@ -239,30 +223,23 @@ contract CappedSTO is CappedSTOStorage, STO, ReentrancyGuard {
239223
_deliverTokens(_beneficiary, _tokenAmount);
240224
}
241225

242-
/**
243-
* @notice Overrides for extensions that require an internal state to check for validity
244-
(current user contributions, etc.)
245-
*/
246-
function _updatePurchasingState(
247-
address, /*_beneficiary*/
248-
uint256 _investedAmount
249-
) internal pure {
250-
_investedAmount = 0; //yolo
251-
}
252-
253226
/**
254227
* @notice Overrides to extend the way in which ether is converted to tokens.
255228
* @param _investedAmount Value in wei to be converted into tokens
256229
* @return Number of tokens that can be purchased with the specified _investedAmount
257230
* @return Remaining amount that should be refunded to the investor
258231
*/
259-
function _getTokenAmount(uint256 _investedAmount) internal view returns(uint256 _tokens, uint256 _refund) {
260-
_tokens = _investedAmount.mul(rate);
261-
_tokens = _tokens.div(uint256(10) ** 18);
232+
function _getTokenAmount(uint256 _investedAmount) internal view returns(uint256 tokens, uint256 refund) {
233+
tokens = _investedAmount.mul(rate);
234+
tokens = tokens.div(uint256(10) ** 18);
235+
if (totalTokensSold.add(tokens) > cap) {
236+
tokens = cap.sub(totalTokensSold);
237+
}
262238
uint256 granularity = ISecurityToken(securityToken).granularity();
263-
_tokens = _tokens.div(granularity);
264-
_tokens = _tokens.mul(granularity);
265-
_refund = _investedAmount.sub((_tokens.mul(uint256(10) ** 18)).div(rate));
239+
tokens = tokens.div(granularity);
240+
tokens = tokens.mul(granularity);
241+
require(tokens > 0, "Cap reached");
242+
refund = _investedAmount.sub((tokens.mul(uint256(10) ** 18)).div(rate));
266243
}
267244

268245
/**

contracts/modules/STO/STO.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ contract STO is ISTO, STOStorage, Module, Pausable {
6363

6464
function _setFundRaiseType(FundRaiseType[] memory _fundRaiseTypes) internal {
6565
// FundRaiseType[] parameter type ensures only valid values for _fundRaiseTypes
66-
require(_fundRaiseTypes.length > 0, "Raise type is not specified");
66+
require(_fundRaiseTypes.length > 0 && _fundRaiseTypes.length <= 3, "Raise type is not specified");
6767
fundRaiseTypes[uint8(FundRaiseType.ETH)] = false;
6868
fundRaiseTypes[uint8(FundRaiseType.POLY)] = false;
6969
fundRaiseTypes[uint8(FundRaiseType.SC)] = false;

contracts/modules/STO/USDTiered/USDTieredSTO.sol

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,11 @@ contract USDTieredSTO is USDTieredSTOStorage, STO {
244244
}
245245
usdTokens = _usdTokens;
246246
for(i = 0; i < _usdTokens.length; i++) {
247-
require(_usdTokens[i] != address(0), "Invalid USD token");
247+
require(_usdTokens[i] != address(0) && _usdTokens[i] != address(polyToken), "Invalid USD token");
248248
usdTokenEnabled[_usdTokens[i]] = true;
249249
}
250250
emit SetAddresses(wallet, _usdTokens);
251-
}
251+
}
252252

253253
////////////////////
254254
// STO Management //
@@ -258,7 +258,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO {
258258
* @notice Finalizes the STO and mint remaining tokens to treasury address
259259
* @notice Treasury wallet address must be whitelisted to successfully finalize
260260
*/
261-
function finalize() public {
261+
function finalize() external {
262262
_onlySecurityTokenOwner();
263263
require(!isFinalized, "STO already finalized");
264264
isFinalized = true;
@@ -275,6 +275,9 @@ contract USDTieredSTO is USDTieredSTOStorage, STO {
275275
}
276276
address walletAddress = (treasuryWallet == address(0) ? IDataStore(getDataStore()).getAddress(TREASURY) : treasuryWallet);
277277
require(walletAddress != address(0), "Invalid address");
278+
uint256 granularity = ISecurityToken(securityToken).granularity();
279+
tempReturned = tempReturned.div(granularity);
280+
tempReturned = tempReturned.mul(granularity);
278281
ISecurityToken(securityToken).issue(walletAddress, tempReturned, "");
279282
emit ReserveTokenMint(msg.sender, walletAddress, tempReturned, currentTier);
280283
finalAmountReturned = tempReturned;
@@ -286,7 +289,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO {
286289
* @param _investors Array of investor addresses to modify
287290
* @param _nonAccreditedLimit Array of uints specifying non-accredited limits
288291
*/
289-
function changeNonAccreditedLimit(address[] memory _investors, uint256[] memory _nonAccreditedLimit) public {
292+
function changeNonAccreditedLimit(address[] calldata _investors, uint256[] calldata _nonAccreditedLimit) external {
290293
_onlySecurityTokenOwner();
291294
//nonAccreditedLimitUSDOverride
292295
require(_investors.length == _nonAccreditedLimit.length, "Length mismatch");
@@ -317,7 +320,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO {
317320
* @notice Function to set allowBeneficialInvestments (allow beneficiary to be different to funder)
318321
* @param _allowBeneficialInvestments Boolean to allow or disallow beneficial investments
319322
*/
320-
function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) public {
323+
function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) external {
321324
_onlySecurityTokenOwner();
322325
require(_allowBeneficialInvestments != allowBeneficialInvestments, "Value unchanged");
323326
allowBeneficialInvestments = _allowBeneficialInvestments;
@@ -535,22 +538,24 @@ contract USDTieredSTO is USDTieredSTOStorage, STO {
535538
internal
536539
returns(uint256 spentUSD, uint256 purchasedTokens, bool gotoNextTier)
537540
{
538-
uint256 maximumTokens = DecimalMath.div(_investedUSD, _tierPrice);
541+
purchasedTokens = DecimalMath.div(_investedUSD, _tierPrice);
539542
uint256 granularity = ISecurityToken(securityToken).granularity();
540-
maximumTokens = maximumTokens.div(granularity);
541-
maximumTokens = maximumTokens.mul(granularity);
542-
if (maximumTokens > _tierRemaining) {
543-
spentUSD = DecimalMath.mul(_tierRemaining, _tierPrice);
544-
// In case of rounding issues, ensure that spentUSD is never more than investedUSD
545-
if (spentUSD > _investedUSD) {
546-
spentUSD = _investedUSD;
547-
}
548-
purchasedTokens = _tierRemaining;
543+
544+
if (purchasedTokens > _tierRemaining) {
545+
purchasedTokens = _tierRemaining.div(granularity);
549546
gotoNextTier = true;
550547
} else {
551-
spentUSD = DecimalMath.mul(maximumTokens, _tierPrice);
552-
purchasedTokens = maximumTokens;
548+
purchasedTokens = purchasedTokens.div(granularity);
553549
}
550+
551+
purchasedTokens = purchasedTokens.mul(granularity);
552+
spentUSD = DecimalMath.mul(purchasedTokens, _tierPrice);
553+
554+
// In case of rounding issues, ensure that spentUSD is never more than investedUSD
555+
if (spentUSD > _investedUSD) {
556+
spentUSD = _investedUSD;
557+
}
558+
554559
if (purchasedTokens > 0) {
555560
ISecurityToken(securityToken).issue(_beneficiary, purchasedTokens, "");
556561
emit TokenPurchase(msg.sender, _beneficiary, purchasedTokens, spentUSD, _tierPrice, _tier);

test/p_usd_tiered_sto.js

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,48 @@ contract("USDTieredSTO", async (accounts) => {
699699
assert.equal(tokens[0], I_DaiToken.address, "USD Tokens should match");
700700
});
701701

702+
it("Should successfully attach the sixth STO module to the security token", async () => {
703+
let stoId = 5; // Non-divisible token with invalid tier
704+
705+
_startTime.push(new BN(currentTime).add(new BN(duration.days(2))));
706+
_endTime.push(new BN(_startTime[stoId]).add(new BN(currentTime).add(new BN(duration.days(100)))));
707+
_ratePerTier.push([new BN(1).mul(e18), new BN(1).mul(e18)]); // [ 1 USD/Token, 1 USD/Token ]
708+
_ratePerTierDiscountPoly.push([new BN(1).mul(e18), new BN(1).mul(e18)]); // [ 1 USD/Token, 1 USD/Token ]
709+
_tokensPerTierTotal.push([new BN(10010).mul(e16), new BN(50).mul(e18)]); // [ 100.1 Token, 50 Token ]
710+
_tokensPerTierDiscountPoly.push([new BN(0), new BN(0)]); // [ 0 Token, 0 Token ]
711+
_nonAccreditedLimitUSD.push(new BN(25).mul(e18)); // [ 25 USD ]
712+
_minimumInvestmentUSD.push(new BN(5));
713+
_fundRaiseTypes.push([0, 1, 2]);
714+
_wallet.push(WALLET);
715+
_treasuryWallet.push(TREASURYWALLET);
716+
_usdToken.push([I_DaiToken.address]);
717+
718+
let config = [
719+
_startTime[stoId],
720+
_endTime[stoId],
721+
_ratePerTier[stoId],
722+
_ratePerTierDiscountPoly[stoId],
723+
_tokensPerTierTotal[stoId],
724+
_tokensPerTierDiscountPoly[stoId],
725+
_nonAccreditedLimitUSD[stoId],
726+
_minimumInvestmentUSD[stoId],
727+
_fundRaiseTypes[stoId],
728+
_wallet[stoId],
729+
_treasuryWallet[stoId],
730+
_usdToken[stoId]
731+
];
732+
733+
let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config);
734+
let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE });
735+
console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey);
736+
assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed");
737+
assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added");
738+
I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module));
739+
// console.log(I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1]);
740+
let tokens = await I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1].getUsdTokens.call();
741+
assert.equal(tokens[0], I_DaiToken.address, "USD Tokens should match");
742+
});
743+
702744
it("Should fail because rates and tier array of different length", async () => {
703745
let stoId = 0;
704746

@@ -1236,7 +1278,8 @@ contract("USDTieredSTO", async (accounts) => {
12361278
await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 });
12371279

12381280
// Change Stable coin address
1239-
await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, TREASURYWALLET, [I_PolyToken.address], { from: ISSUER });
1281+
let I_DaiToken2 = await PolyTokenFaucet.new();
1282+
await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, TREASURYWALLET, [I_DaiToken2.address], { from: ISSUER });
12401283

12411284
// NONACCREDITED DAI
12421285
await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 }));
@@ -3894,6 +3937,114 @@ contract("USDTieredSTO", async (accounts) => {
38943937
await I_SecurityToken.changeGranularity(1, { from: ISSUER });
38953938
});
38963939

3940+
it("should successfully buy a granular amount when buying indivisible token with illegal tier limits", async () => {
3941+
await I_SecurityToken.changeGranularity(e18, { from: ISSUER });
3942+
let stoId = 5;
3943+
let tierId = 0;
3944+
let investment_Tokens = new BN(110).mul(e18);
3945+
let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Tokens);
3946+
3947+
let refund_Tokens = new BN(0);
3948+
let refund_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", refund_Tokens);
3949+
3950+
await I_PolyToken.getTokens(investment_POLY, ACCREDITED1);
3951+
await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 });
3952+
3953+
let init_TokenSupply = await I_SecurityToken.totalSupply();
3954+
let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1);
3955+
let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1));
3956+
let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1);
3957+
let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold();
3958+
let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address));
3959+
let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address);
3960+
let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH);
3961+
let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY);
3962+
let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET));
3963+
let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET);
3964+
3965+
let tokensToMint = (await I_USDTieredSTO_Array[stoId].buyWithPOLY.call(ACCREDITED1, investment_POLY, {from: ACCREDITED1}))[2];
3966+
3967+
// Buy With POLY
3968+
let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, {
3969+
from: ACCREDITED1,
3970+
gasPrice: GAS_PRICE
3971+
});
3972+
let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed));
3973+
console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey);
3974+
3975+
let final_TokenSupply = await I_SecurityToken.totalSupply();
3976+
let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1);
3977+
let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1));
3978+
let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1);
3979+
let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold();
3980+
let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address));
3981+
let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address);
3982+
let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH);
3983+
let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY);
3984+
let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET));
3985+
let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET);
3986+
3987+
assert.equal(
3988+
final_TokenSupply.toString(),
3989+
init_TokenSupply
3990+
.add(investment_Tokens)
3991+
.sub(refund_Tokens)
3992+
.toString(),
3993+
"Token Supply not changed as expected"
3994+
);
3995+
assert.equal(tokensToMint.toString(), investment_Tokens.sub(refund_Tokens).toString(), "View function returned incorrect data");
3996+
assert.equal(
3997+
final_InvestorTokenBal.toString(),
3998+
init_InvestorTokenBal
3999+
.add(investment_Tokens)
4000+
.sub(refund_Tokens)
4001+
.toString(),
4002+
"Investor Token Balance not changed as expected"
4003+
);
4004+
assert.equal(
4005+
final_InvestorETHBal.toString(),
4006+
init_InvestorETHBal.sub(gasCost2).toString(),
4007+
"Investor ETH Balance not changed as expected"
4008+
);
4009+
assert.equal(
4010+
final_InvestorPOLYBal.toString(),
4011+
init_InvestorPOLYBal
4012+
.sub(investment_POLY)
4013+
.add(refund_POLY)
4014+
.toString(),
4015+
"Investor POLY Balance not changed as expected"
4016+
);
4017+
assert.equal(
4018+
final_STOTokenSold.toString(),
4019+
init_STOTokenSold
4020+
.add(investment_Tokens)
4021+
.sub(refund_Tokens)
4022+
.toString(),
4023+
"STO Token Sold not changed as expected"
4024+
);
4025+
assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected");
4026+
assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected");
4027+
assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected");
4028+
assert.equal(
4029+
final_RaisedPOLY.toString(),
4030+
init_RaisedPOLY
4031+
.add(investment_POLY)
4032+
.sub(refund_POLY)
4033+
.toString(),
4034+
"Raised POLY not changed as expected"
4035+
);
4036+
assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected");
4037+
assert.equal(
4038+
final_WalletPOLYBal.toString(),
4039+
init_WalletPOLYBal
4040+
.add(investment_POLY)
4041+
.sub(refund_POLY)
4042+
.toString(),
4043+
"Wallet POLY Balance not changed as expected"
4044+
);
4045+
await I_SecurityToken.changeGranularity(1, { from: ISSUER });
4046+
});
4047+
38974048
it("should successfully buy a granular amount and refund balance when buying indivisible token with ETH", async () => {
38984049
await I_SecurityToken.changeGranularity(e18, { from: ISSUER });
38994050
let stoId = 4;

0 commit comments

Comments
 (0)