Skip to content

Commit d771ea4

Browse files
authored
Merge pull request #415 from PolymathNetwork/capped-sto-fix
Added support for indivisible tokens in capped STO
2 parents 4c352ee + 4837af9 commit d771ea4

File tree

3 files changed

+57
-43
lines changed

3 files changed

+57
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
77

88
## CappedSTO 2.0.1
99
* `rate` is now accepted as multiplied by 10^18 to allow settting higher price than 1ETH/POLY per token.
10+
* Indivisble tokens are now supported. When trying to buy partial tokens, allowed full units of tokens will be purchased and remaining funds will be returned.
1011

1112
## USDTieredSTO 2.1.0
1213
* Added `buyTokensView` and `getTokensMintedByTier` to USDTSTO.

contracts/modules/STO/CappedSTO.sol

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ contract CappedSTO is ISTO, ReentrancyGuard {
6666
public
6767
onlyFactory
6868
{
69+
require(endTime == 0, "Already configured");
6970
require(_rate > 0, "Rate of token should be greater than 0");
7071
require(_fundsReceiver != address(0), "Zero address is not permitted");
7172
/*solium-disable-next-line security/no-block-members*/
@@ -110,9 +111,10 @@ contract CappedSTO is ISTO, ReentrancyGuard {
110111
require(fundRaiseTypes[uint8(FundRaiseType.ETH)], "Mode of investment is not ETH");
111112

112113
uint256 weiAmount = msg.value;
113-
_processTx(_beneficiary, weiAmount);
114+
uint256 refund = _processTx(_beneficiary, weiAmount);
115+
weiAmount = weiAmount.sub(refund);
114116

115-
_forwardFunds();
117+
_forwardFunds(refund);
116118
_postValidatePurchase(_beneficiary, weiAmount);
117119
}
118120

@@ -123,9 +125,9 @@ contract CappedSTO is ISTO, ReentrancyGuard {
123125
function buyTokensWithPoly(uint256 _investedPOLY) public nonReentrant{
124126
require(!paused, "Should not be paused");
125127
require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "Mode of investment is not POLY");
126-
_processTx(msg.sender, _investedPOLY);
127-
_forwardPoly(msg.sender, wallet, _investedPOLY);
128-
_postValidatePurchase(msg.sender, _investedPOLY);
128+
uint256 refund = _processTx(msg.sender, _investedPOLY);
129+
_forwardPoly(msg.sender, wallet, _investedPOLY.sub(refund));
130+
_postValidatePurchase(msg.sender, _investedPOLY.sub(refund));
129131
}
130132

131133
/**
@@ -183,11 +185,13 @@ contract CappedSTO is ISTO, ReentrancyGuard {
183185
* @param _beneficiary Address performing the token purchase
184186
* @param _investedAmount Value in wei involved in the purchase
185187
*/
186-
function _processTx(address _beneficiary, uint256 _investedAmount) internal {
188+
function _processTx(address _beneficiary, uint256 _investedAmount) internal returns(uint256 refund) {
187189

188190
_preValidatePurchase(_beneficiary, _investedAmount);
189191
// calculate token amount to be created
190-
uint256 tokens = _getTokenAmount(_investedAmount);
192+
uint256 tokens;
193+
(tokens, refund) = _getTokenAmount(_investedAmount);
194+
_investedAmount = _investedAmount.sub(refund);
191195

192196
// update state
193197
if (fundRaiseTypes[uint8(FundRaiseType.POLY)]) {
@@ -212,7 +216,9 @@ contract CappedSTO is ISTO, ReentrancyGuard {
212216
function _preValidatePurchase(address _beneficiary, uint256 _investedAmount) internal view {
213217
require(_beneficiary != address(0), "Beneficiary address should not be 0x");
214218
require(_investedAmount != 0, "Amount invested should not be equal to 0");
215-
require(totalTokensSold.add(_getTokenAmount(_investedAmount)) <= cap, "Investment more than cap is not allowed");
219+
uint256 tokens;
220+
(tokens, ) = _getTokenAmount(_investedAmount);
221+
require(totalTokensSold.add(tokens) <= cap, "Investment more than cap is not allowed");
216222
/*solium-disable-next-line security/no-block-members*/
217223
require(now >= startTime && now <= endTime, "Offering is closed/Not yet started");
218224
}
@@ -261,18 +267,23 @@ contract CappedSTO is ISTO, ReentrancyGuard {
261267
* @notice Overrides to extend the way in which ether is converted to tokens.
262268
* @param _investedAmount Value in wei to be converted into tokens
263269
* @return Number of tokens that can be purchased with the specified _investedAmount
270+
* @return Remaining amount that should be refunded to the investor
264271
*/
265-
function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256 tokenAmount) {
266-
tokenAmount = _investedAmount.mul(rate);
267-
tokenAmount = tokenAmount.div(uint256(10) ** 18);
268-
return tokenAmount;
272+
function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256 _tokens, uint256 _refund) {
273+
_tokens = _investedAmount.mul(rate);
274+
_tokens = _tokens.div(uint256(10) ** 18);
275+
uint256 granularity = ISecurityToken(securityToken).granularity();
276+
_tokens = _tokens.div(granularity);
277+
_tokens = _tokens.mul(granularity);
278+
_refund = _investedAmount.sub((_tokens.mul(uint256(10) ** 18)).div(rate));
269279
}
270280

271281
/**
272282
* @notice Determines how ETH is stored/forwarded on purchases.
273283
*/
274-
function _forwardFunds() internal {
275-
wallet.transfer(msg.value);
284+
function _forwardFunds(uint256 _refund) internal {
285+
wallet.transfer(msg.value.sub(_refund));
286+
msg.sender.transfer(_refund);
276287
}
277288

278289
/**

test/b_capped_sto.js

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -338,16 +338,6 @@ contract("CappedSTO", accounts => {
338338
);
339339
});
340340

341-
it("Should buy the tokens -- Failed due to wrong granularity", async () => {
342-
await catchRevert(
343-
web3.eth.sendTransaction({
344-
from: account_investor1,
345-
to: I_CappedSTO_Array_ETH[0].address,
346-
value: web3.utils.toWei("0.1111", "ether")
347-
})
348-
);
349-
});
350-
351341
it("Should Buy the tokens", async () => {
352342
blockNo = latestBlock();
353343
fromTime = latestTime();
@@ -427,17 +417,8 @@ contract("CappedSTO", accounts => {
427417
assert.isFalse(await I_CappedSTO_Array_ETH[0].paused.call());
428418
});
429419

430-
it("Should buy the tokens -- Failed due to wrong granularity", async () => {
431-
await catchRevert(
432-
web3.eth.sendTransaction({
433-
from: account_investor1,
434-
to: I_CappedSTO_Array_ETH[0].address,
435-
value: web3.utils.toWei("0.1111", "ether")
436-
})
437-
);
438-
});
439-
440-
it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => {
420+
it("Should buy the granular unit tokens and refund pending amount", async () => {
421+
await I_SecurityToken_ETH.changeGranularity(10 ** 21, {from: token_owner});
441422
let tx = await I_GeneralTransferManager.modifyWhitelist(
442423
account_investor2,
443424
fromTime,
@@ -448,15 +429,26 @@ contract("CappedSTO", accounts => {
448429
from: account_issuer
449430
}
450431
);
451-
452432
assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist");
433+
const initBalance = BigNumber(await web3.eth.getBalance(account_investor2));
434+
tx = await I_CappedSTO_Array_ETH[0].buyTokens(account_investor2, {from: account_investor2, value: web3.utils.toWei("1.5", "ether"), gasPrice: 1});
435+
const finalBalance = BigNumber(await web3.eth.getBalance(account_investor2));
436+
assert.equal(finalBalance.add(BigNumber(tx.receipt.gasUsed)).add(web3.utils.toWei("1", "ether")).toNumber(), initBalance.toNumber());
437+
await I_SecurityToken_ETH.changeGranularity(1, {from: token_owner});
438+
assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 2);
439+
440+
assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000);
441+
});
442+
443+
it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => {
444+
453445

454446
// Fallback transaction
455447
await web3.eth.sendTransaction({
456448
from: account_investor2,
457449
to: I_CappedSTO_Array_ETH[0].address,
458450
gas: 2100000,
459-
value: web3.utils.toWei("9", "ether")
451+
value: web3.utils.toWei("8", "ether")
460452
});
461453

462454
assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10);
@@ -793,8 +785,8 @@ contract("CappedSTO", accounts => {
793785
await I_CappedSTO_Array_POLY[0].unpause({ from: account_issuer });
794786
});
795787

796-
797-
it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => {
788+
it("Should buy the granular unit tokens and charge only required POLY", async () => {
789+
await I_SecurityToken_POLY.changeGranularity(10 ** 22, {from: token_owner});
798790
let tx = await I_GeneralTransferManager.modifyWhitelist(
799791
account_investor2,
800792
P_fromTime,
@@ -806,15 +798,25 @@ contract("CappedSTO", accounts => {
806798
gas: 500000
807799
}
808800
);
809-
801+
console.log((await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber());
810802
assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist");
811-
812803
await I_PolyToken.getTokens(10000 * Math.pow(10, 18), account_investor2);
813-
814804
await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 9000 * Math.pow(10, 18), { from: account_investor2 });
805+
const initRaised = (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber();
806+
tx = await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(3000 * Math.pow(10, 18), { from: account_investor2 });
807+
await I_SecurityToken_POLY.changeGranularity(1, {from: token_owner});
808+
assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), initRaised + 2000); //2000 this call, 1000 earlier
809+
assert.equal((await I_PolyToken.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 8000);
810+
assert.equal(
811+
(await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(),
812+
10000
813+
);
814+
});
815815

816+
817+
it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => {
816818
// buyTokensWithPoly transaction
817-
await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(9000 * Math.pow(10, 18), { from: account_investor2 });
819+
await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(7000 * Math.pow(10, 18), { from: account_investor2 });
818820

819821
assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10000);
820822

0 commit comments

Comments
 (0)