From abe233d8fa69980c3770c150e971ce944f5d444a Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Wed, 14 Nov 2018 21:37:31 +0530 Subject: [PATCH 1/5] Support for indivisible tokens --- contracts/modules/STO/CappedSTO.sol | 36 +++++++++++++++++++---------- test/b_capped_sto.js | 11 ++++----- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/contracts/modules/STO/CappedSTO.sol b/contracts/modules/STO/CappedSTO.sol index 60373ae37..24a3e7c23 100644 --- a/contracts/modules/STO/CappedSTO.sol +++ b/contracts/modules/STO/CappedSTO.sol @@ -64,6 +64,7 @@ contract CappedSTO is ISTO, ReentrancyGuard { public onlyFactory { + require(endTime == 0, "Already configured"); require(_rate > 0, "Rate of token should be greater than 0"); require(_fundsReceiver != address(0), "Zero address is not permitted"); /*solium-disable-next-line security/no-block-members*/ @@ -108,9 +109,10 @@ contract CappedSTO is ISTO, ReentrancyGuard { require(fundRaiseTypes[uint8(FundRaiseType.ETH)], "Mode of investment is not ETH"); uint256 weiAmount = msg.value; - _processTx(_beneficiary, weiAmount); + uint256 refund = _processTx(_beneficiary, weiAmount); + weiAmount = weiAmount.sub(refund); - _forwardFunds(); + _forwardFunds(refund); _postValidatePurchase(_beneficiary, weiAmount); } @@ -121,9 +123,9 @@ contract CappedSTO is ISTO, ReentrancyGuard { function buyTokensWithPoly(uint256 _investedPOLY) public nonReentrant{ require(!paused, "Should not be paused"); require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "Mode of investment is not POLY"); - _processTx(msg.sender, _investedPOLY); - _forwardPoly(msg.sender, wallet, _investedPOLY); - _postValidatePurchase(msg.sender, _investedPOLY); + uint256 refund = _processTx(msg.sender, _investedPOLY); + _forwardPoly(msg.sender, wallet, _investedPOLY.sub(refund)); + _postValidatePurchase(msg.sender, _investedPOLY.sub(refund)); } /** @@ -180,11 +182,13 @@ contract CappedSTO is ISTO, ReentrancyGuard { * @param _beneficiary Address performing the token purchase * @param _investedAmount Value in wei involved in the purchase */ - function _processTx(address _beneficiary, uint256 _investedAmount) internal { + function _processTx(address _beneficiary, uint256 _investedAmount) internal returns(uint256 refund) { _preValidatePurchase(_beneficiary, _investedAmount); // calculate token amount to be created - uint256 tokens = _getTokenAmount(_investedAmount); + uint256 tokens; + (tokens, refund) = _getTokenAmount(_investedAmount); + _investedAmount = _investedAmount.sub(refund); // update state if (fundRaiseTypes[uint8(FundRaiseType.POLY)]) { @@ -209,7 +213,9 @@ contract CappedSTO is ISTO, ReentrancyGuard { function _preValidatePurchase(address _beneficiary, uint256 _investedAmount) internal view { require(_beneficiary != address(0), "Beneficiary address should not be 0x"); require(_investedAmount != 0, "Amount invested should not be equal to 0"); - require(totalTokensSold.add(_getTokenAmount(_investedAmount)) <= cap, "Investment more than cap is not allowed"); + uint256 tokens; + (tokens, ) = _getTokenAmount(_investedAmount); + require(totalTokensSold.add(tokens) <= cap, "Investment more than cap is not allowed"); /*solium-disable-next-line security/no-block-members*/ require(now >= startTime && now <= endTime, "Offering is closed/Not yet started"); } @@ -258,16 +264,22 @@ contract CappedSTO is ISTO, ReentrancyGuard { * @notice Overrides to extend the way in which ether is converted to tokens. * @param _investedAmount Value in wei to be converted into tokens * @return Number of tokens that can be purchased with the specified _investedAmount + * @return Remaining amount that should be refunded to the investor */ - function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256) { - return _investedAmount.mul(rate); + function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256 _tokens, uint256 _refund) { + _tokens = _investedAmount.mul(rate); + uint256 granularity = ISecurityToken(securityToken).granularity(); + _tokens = _tokens.div(granularity); + _tokens = _tokens.mul(granularity); + _refund = _investedAmount.sub(_tokens.div(rate)); } /** * @notice Determines how ETH is stored/forwarded on purchases. */ - function _forwardFunds() internal { - wallet.transfer(msg.value); + function _forwardFunds(uint256 _refund) internal { + wallet.transfer(msg.value.sub(_refund)); + msg.sender.transfer(_refund); } /** diff --git a/test/b_capped_sto.js b/test/b_capped_sto.js index d89fe6eba..cd2d05b2f 100644 --- a/test/b_capped_sto.js +++ b/test/b_capped_sto.js @@ -427,14 +427,13 @@ contract("CappedSTO", accounts => { assert.isFalse(await I_CappedSTO_Array_ETH[0].paused.call()); }); - it("Should buy the tokens -- Failed due to wrong granularity", async () => { - await catchRevert( + it("Should buy the granular unit tokens and refund pending amount", async () => { + web3.eth.sendTransaction({ from: account_investor1, to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("0.1111", "ether") - }) - ); + value: web3.utils.toWei("1.1", "ether") + }); }); it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { @@ -456,7 +455,7 @@ contract("CappedSTO", accounts => { from: account_investor2, to: I_CappedSTO_Array_ETH[0].address, gas: 2100000, - value: web3.utils.toWei("9", "ether") + value: web3.utils.toWei("8", "ether") }); assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10); From a1dccb8d04c6e093366e703fb8cd69533b521a49 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Thu, 15 Nov 2018 11:58:56 +0530 Subject: [PATCH 2/5] test cases for divible token capped STO --- test/b_capped_sto.js | 55 +++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/test/b_capped_sto.js b/test/b_capped_sto.js index cd2d05b2f..8cc0beacf 100644 --- a/test/b_capped_sto.js +++ b/test/b_capped_sto.js @@ -338,16 +338,6 @@ contract("CappedSTO", accounts => { ); }); - it("Should buy the tokens -- Failed due to wrong granularity", async () => { - await catchRevert( - web3.eth.sendTransaction({ - from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("0.1111", "ether") - }) - ); - }); - it("Should Buy the tokens", async () => { blockNo = latestBlock(); fromTime = latestTime(); @@ -428,15 +418,7 @@ contract("CappedSTO", accounts => { }); it("Should buy the granular unit tokens and refund pending amount", async () => { - - web3.eth.sendTransaction({ - from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("1.1", "ether") - }); - }); - - it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { + await I_SecurityToken_ETH.changeGranularity(10 ** 21, {from: token_owner}); let tx = await I_GeneralTransferManager.modifyWhitelist( account_investor2, fromTime, @@ -447,8 +429,19 @@ contract("CappedSTO", accounts => { from: account_issuer } ); - assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); + const initBalance = BigNumber(await web3.eth.getBalance(account_investor2)); + tx = await I_CappedSTO_Array_ETH[0].buyTokens(account_investor2, {from: account_investor2, value: web3.utils.toWei("1.5", "ether"), gasPrice: 1}); + const finalBalance = BigNumber(await web3.eth.getBalance(account_investor2)); + assert.equal(finalBalance.add(BigNumber(tx.receipt.gasUsed)).add(web3.utils.toWei("1", "ether")).toNumber(), initBalance.toNumber()); + await I_SecurityToken_ETH.changeGranularity(1, {from: token_owner}); + assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 2); + + assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + }); + + it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { + // Fallback transaction await web3.eth.sendTransaction({ @@ -792,8 +785,8 @@ contract("CappedSTO", accounts => { await I_CappedSTO_Array_POLY[0].unpause({ from: account_issuer }); }); - - it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { + it("Should buy the granular unit tokens and charge only required POLY", async () => { + await I_SecurityToken_POLY.changeGranularity(10 ** 22, {from: token_owner}); let tx = await I_GeneralTransferManager.modifyWhitelist( account_investor2, P_fromTime, @@ -805,15 +798,25 @@ contract("CappedSTO", accounts => { gas: 500000 } ); - + console.log((await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber()); assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); - await I_PolyToken.getTokens(10000 * Math.pow(10, 18), account_investor2); - await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 9000 * Math.pow(10, 18), { from: account_investor2 }); + const initRaised = (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(); + tx = await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(3000 * Math.pow(10, 18), { from: account_investor2 }); + await I_SecurityToken_POLY.changeGranularity(1, {from: token_owner}); + 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 + assert.equal((await I_PolyToken.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 8000); + assert.equal( + (await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + 10000 + ); + }); + + it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { // buyTokensWithPoly transaction - await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(9000 * Math.pow(10, 18), { from: account_investor2 }); + await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(7000 * Math.pow(10, 18), { from: account_investor2 }); assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10000); From f3a872234cec43574b3777bb0fbca384111dae27 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Thu, 6 Dec 2018 19:57:23 +0530 Subject: [PATCH 3/5] Merge fix --- contracts/modules/STO/CappedSTO.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/modules/STO/CappedSTO.sol b/contracts/modules/STO/CappedSTO.sol index 5b37b5437..af091dacf 100644 --- a/contracts/modules/STO/CappedSTO.sol +++ b/contracts/modules/STO/CappedSTO.sol @@ -275,7 +275,7 @@ contract CappedSTO is ISTO, ReentrancyGuard { uint256 granularity = ISecurityToken(securityToken).granularity(); _tokens = _tokens.div(granularity); _tokens = _tokens.mul(granularity); - _refund = _investedAmount.sub(_tokens.div(rate)); + _refund = _investedAmount.sub((_tokens.mul(uint256(10) ** 18)).div(rate)); } /** From c85c52178aaadf91a0847d8dd4002a9858a50f40 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Mon, 10 Dec 2018 22:38:09 +0530 Subject: [PATCH 4/5] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71457d9d9..d12951de2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ## CappedSTO 2.0.1 * `rate` is now accepted as multiplied by 10^18 to allow settting higher price than 1ETH/POLY per token. +* Indivisble tokens are now supported. When trying to buy partial tokens, allowed full units of tokens will be purchased and rest of the funds will be returned. ## USDTieredSTO 2.0.1 * Added `buyTokensView` and `getTokensMintedByTier` to USDTSTO. From 1423a118ee4ddf82fa3ec10f99484fc2dd1e2957 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Mon, 10 Dec 2018 22:39:27 +0530 Subject: [PATCH 5/5] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d12951de2..dd53ce781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. ## CappedSTO 2.0.1 * `rate` is now accepted as multiplied by 10^18 to allow settting higher price than 1ETH/POLY per token. -* Indivisble tokens are now supported. When trying to buy partial tokens, allowed full units of tokens will be purchased and rest of the funds will be returned. +* 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. ## USDTieredSTO 2.0.1 * Added `buyTokensView` and `getTokensMintedByTier` to USDTSTO.