The 2018 guide to writing (and testing) real world crowdsale contracts
It's no secret - ICOs are all the rage these days. Many new companies are raising millions of dollars by selling their tokens in crowdsale. If you are reading this article, you are probably pursuing the idea of doing an ICO.
I wrote an article a few days back where I explained how one can quickly create their own tokens and sell them in an ICO. Although it offers good insights into ICO Contracts, it omits many real word aspects such as presale, public sale, discounts, security etc.
I am writing this article to demonstrate the creation of a real-world Crowdsale Smart Contract with the following improvements/characteristics:
- A Mintable ERC20 token where total supply increases as people purchase the tokens
- A refundable crowdsale with a goal and hard cap
- Presale support
- Ability to offer Presale discount
- Security
Update: I'm writing a book on Ethereum Smart Contracts. It's available for purchase on LeanPub. If you are interested in learning more about Smart Contracts and ICOs, feel free to give it a try.
Here are some of the tools we are going to use:
- node 8 & npm
- Truffle Framework
- Ganache
- Open Zeppelin
- MetaMask
The corresponding codebase is available on GitHub. Feel free to clone it and play around.
Installing Truffle
Truffle framework is the easiest way to build and test decentralized apps/contracts. I assume you already have node 8. If not, use nvm to install node 8 (with nvm you can maintain multiple node versions).
To install truffle run the following command:
npm install -g truffle
Now truffle is installed as a global node module.
Installing Ganache
Ganache lets you run a personal Ethereum blockchain on your computer. This is where you will deploy your contracts while testing locally. Head over to Ganache website to install the latest version for your platform. Once downloaded, install the software and click on the app icon to run it.
Once started, it looks something like this:
Setting up the project
Go to your workspace and create your project directory. I am going to name it HashnodeCrowdsale
:
mkdir HashnodeCrowdsale && cd HashnodeCrowdsale
Now initialize the truffle project by running the following command:
truffle init
Once the command finishes you will see the following directory structure:
HashnodeCrowdsale
contracts/
migrations/
test/
truffle.js
truffle-config.js
As the name suggests, all your Solidity source files go inside contracts/
directory. After you compile your files for the first time, you can see a build/
directory which contains all your compiled code.
Adding Open Zeppelin
You shouldn't reinvent the wheel when it comes to money. When writing Smart Contracts you should always strive to write less code. This is because less code means lesser number of bugs. Also, it is of paramount importance to write well tested and secure solidity code - you don't want to mess with people's money. 😃
Due to the above reason we'll use a library called Open Zeppelin
. It has a bunch of well tested and secure Crowdsale/Token contracts. Instead of writing ERC20 tokens and Crowdsale contracts from scratch we'll use Open Zeppelin as a base contract and build upon it.
To install Open Zeppelin, run the following command:
npm install zeppelin-solidity@1.5.0
At the time of writing I am using zeppelin-solidity@1.5.0
. The above command should install the zeppelin-solidity
package inside node_modules
. You can find the token & crowdsale contracts inside node_modules/zeppelin-solidity/contracts
directory. We'll import these from our own solidity code.
Code
In order to launch a crowdsale we need to write our ERC20 token. So, go ahead and paste the following code into contracts/HashnodeToken.sol
(you need to create this file):
HashnodeToken.sol
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/token/MintableToken.sol';
contract HashnodeToken is MintableToken {
string public name = "Hashnode Token";
string public symbol = "HT";
uint8 public decimals = 18;
}
As you may have guessed, we are just extending MintableToken
provided by zeppelin-solidity
package. MintableToken
itself inherits ERC20
token contract (find it inside zeppelin-solidity/contracts/token/
directory). So, the end result is that our new token HashnodeToken
is an ERC20 token.
MintableToken means that the total supply of the token starts with 0 and increases as people purchase the tokens in the crowdsale. If you decide to create 100 tokens and sell 60 of them in crowdsale, the supply will increase up to 60 (as people pay ETH and buy the tokens). Once the crowdsale is over, 40 more tokens will be minted making the total supply 100.
Feel free to change the above code and supply appropriate values for name
and symbol
. It's recommended to set the decimals to 18
in order to be standard compliant.
Now that our ERC20 token is ready, let's proceed to code our crowdsale contract. Create the file contracts/HashnodeCrowdsale.sol
and paste the following content:
HashnodeCrowdsale.sol
pragma solidity ^0.4.18;
import './HashnodeToken.sol';
import 'zeppelin-solidity/contracts/crowdsale/CappedCrowdsale.sol';
import 'zeppelin-solidity/contracts/crowdsale/RefundableCrowdsale.sol';
contract HashnodeCrowdsale is CappedCrowdsale, RefundableCrowdsale {
// ICO Stage
// ============
enum CrowdsaleStage { PreICO, ICO }
CrowdsaleStage public stage = CrowdsaleStage.PreICO; // By default it's Pre Sale
// =============
// Token Distribution
// =============================
uint256 public maxTokens = 100000000000000000000; // There will be total 100 Hashnode Tokens
uint256 public tokensForEcosystem = 20000000000000000000;
uint256 public tokensForTeam = 10000000000000000000;
uint256 public tokensForBounty = 10000000000000000000;
uint256 public totalTokensForSale = 60000000000000000000; // 60 HTs will be sold in Crowdsale
uint256 public totalTokensForSaleDuringPreICO = 20000000000000000000; // 20 out of 60 HTs will be sold during PreICO
// ==============================
// Amount raised in PreICO
// ==================
uint256 public totalWeiRaisedDuringPreICO;
// ===================
// Events
event EthTransferred(string text);
event EthRefunded(string text);
// Constructor
// ============
function HashnodeCrowdsale(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet, uint256 _goal, uint256 _cap) CappedCrowdsale(_cap) FinalizableCrowdsale() RefundableCrowdsale(_goal) Crowdsale(_startTime, _endTime, _rate, _wallet) public {
require(_goal <= _cap);
}
// =============
// Token Deployment
// =================
function createTokenContract() internal returns (MintableToken) {
return new HashnodeToken(); // Deploys the ERC20 token. Automatically called when crowdsale contract is deployed
}
// ==================
// Crowdsale Stage Management
// =========================================================
// Change Crowdsale Stage. Available Options: PreICO, ICO
function setCrowdsaleStage(uint value) public onlyOwner {
CrowdsaleStage _stage;
if (uint(CrowdsaleStage.PreICO) == value) {
_stage = CrowdsaleStage.PreICO;
} else if (uint(CrowdsaleStage.ICO) == value) {
_stage = CrowdsaleStage.ICO;
}
stage = _stage;
if (stage == CrowdsaleStage.PreICO) {
setCurrentRate(5);
} else if (stage == CrowdsaleStage.ICO) {
setCurrentRate(2);
}
}
// Change the current rate
function setCurrentRate(uint256 _rate) private {
rate = _rate;
}
// ================ Stage Management Over =====================
// Token Purchase
// =========================
function () external payable {
uint256 tokensThatWillBeMintedAfterPurchase = msg.value.mul(rate);
if ((stage == CrowdsaleStage.PreICO) && (token.totalSupply() + tokensThatWillBeMintedAfterPurchase > totalTokensForSaleDuringPreICO)) {
msg.sender.transfer(msg.value); // Refund them
EthRefunded("PreICO Limit Hit");
return;
}
buyTokens(msg.sender);
if (stage == CrowdsaleStage.PreICO) {
totalWeiRaisedDuringPreICO = totalWeiRaisedDuringPreICO.add(msg.value);
}
}
function forwardFunds() internal {
if (stage == CrowdsaleStage.PreICO) {
wallet.transfer(msg.value);
EthTransferred("forwarding funds to wallet");
} else if (stage == CrowdsaleStage.ICO) {
EthTransferred("forwarding funds to refundable vault");
super.forwardFunds();
}
}
// ===========================
// Finish: Mint Extra Tokens as needed before finalizing the Crowdsale.
// ====================================================================
function finish(address _teamFund, address _ecosystemFund, address _bountyFund) public onlyOwner {
require(!isFinalized);
uint256 alreadyMinted = token.totalSupply();
require(alreadyMinted < maxTokens);
uint256 unsoldTokens = totalTokensForSale - alreadyMinted;
if (unsoldTokens > 0) {
tokensForEcosystem = tokensForEcosystem + unsoldTokens;
}
token.mint(_teamFund,tokensForTeam);
token.mint(_ecosystemFund,tokensForEcosystem);
token.mint(_bountyFund,tokensForBounty);
finalize();
}
// ===============================
// REMOVE THIS FUNCTION ONCE YOU ARE READY FOR PRODUCTION
// USEFUL FOR TESTING `finish()` FUNCTION
function hasEnded() public view returns (bool) {
return true;
}
}
The code is pretty self-explanatory and is well commented. Let me outline a few important points:
- Our crowdsale contract inherits
CappedCrowdsale
andRefundableCrowdsale
(supplied byzeppelin-solidity
) and therefore has a goal and a hard cap. If the contract isn't able to raise a certain minimum amount of ETH during the crowdsale, the ETH amounts will be refunded to the investors. Similarly, the contract will not be able to raise more than a specific amount of ETH due to a hard cap. - Total 100 tokens will be created by the end of the crowdsale. Out of 100, 60 will be sold in the crowdsale. Once the crowdsale is over, rest 40 tokens will be (minted and) divided among three wallets such as
teamFund
,ecosystemFund
andbountyFund
. - The crowdsale has two stages: PreICO and ICO. You can change the stage and update
rate
variable to offer extra discounts during presale. As per the above crowdsale contract 1 ETH can buy 5 tokens in PreICO and just 2 tokens in public sale. So, the early investors get extra discounts. Note: Max 20 tokens will be sold in PreICO. - When PreICO is live, the incoming ETH amounts are immediately transferred to the beneficiary wallet (supplied while deploying the contract). However, in the public sale the raised ETH amounts are sent to a refund vault. If the crowdsale reaches its goal, the funds are transferred to the beneficiary wallet. Otherwise, investors are allowed to claim refunds (check
zeppelin-solidity/contracts/crowdsale/RefundVault.sol
). - You have to call
finish()
to close the crowdsale. This is where remaining tokens are minted and distributed among various reserved funds. Note: Any unsold tokens are added to the ecocystem fund.
Test and Deploy
Now that we are done coding the contract let's write a bunch of tests to make sure that everything is working as expected. So, create a new file called test/TestCrowdsale.js
and paste the following:
var HashnodeCrowdsale = artifacts.require("HashnodeCrowdsale");
var HashnodeToken = artifacts.require("HashnodeToken");
contract('HashnodeCrowdsale', function(accounts) {
it('should deploy the token and store the address', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
const token = await instance.token.call();
assert(token, 'Token address couldn\'t be stored');
done();
});
});
it('should set stage to PreICO', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
await instance.setCrowdsaleStage(0);
const stage = await instance.stage.call();
assert.equal(stage.toNumber(), 0, 'The stage couldn\'t be set to PreICO');
done();
});
});
it('one ETH should buy 5 Hashnode Tokens in PreICO', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
const data = await instance.sendTransaction({ from: accounts[7], value: web3.toWei(1, "ether")});
const tokenAddress = await instance.token.call();
const hashnodeToken = HashnodeToken.at(tokenAddress);
const tokenAmount = await hashnodeToken.balanceOf(accounts[7]);
assert.equal(tokenAmount.toNumber(), 5000000000000000000, 'The sender didn\'t receive the tokens as per PreICO rate');
done();
});
});
it('should transfer the ETH to wallet immediately in Pre ICO', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
let balanceOfBeneficiary = await web3.eth.getBalance(accounts[9]);
balanceOfBeneficiary = Number(balanceOfBeneficiary.toString(10));
await instance.sendTransaction({ from: accounts[1], value: web3.toWei(2, "ether")});
let newBalanceOfBeneficiary = await web3.eth.getBalance(accounts[9]);
newBalanceOfBeneficiary = Number(newBalanceOfBeneficiary.toString(10));
assert.equal(newBalanceOfBeneficiary, balanceOfBeneficiary + 2000000000000000000, 'ETH couldn\'t be transferred to the beneficiary');
done();
});
});
it('should set variable `totalWeiRaisedDuringPreICO` correctly', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
var amount = await instance.totalWeiRaisedDuringPreICO.call();
assert.equal(amount.toNumber(), web3.toWei(3, "ether"), 'Total ETH raised in PreICO was not calculated correctly');
done();
});
});
it('should set stage to ICO', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
await instance.setCrowdsaleStage(1);
const stage = await instance.stage.call();
assert.equal(stage.toNumber(), 1, 'The stage couldn\'t be set to ICO');
done();
});
});
it('one ETH should buy 2 Hashnode Tokens in ICO', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
const data = await instance.sendTransaction({ from: accounts[2], value: web3.toWei(1.5, "ether")});
const tokenAddress = await instance.token.call();
const hashnodeToken = HashnodeToken.at(tokenAddress);
const tokenAmount = await hashnodeToken.balanceOf(accounts[2]);
assert.equal(tokenAmount.toNumber(), 3000000000000000000, 'The sender didn\'t receive the tokens as per ICO rate');
done();
});
});
it('should transfer the raised ETH to RefundVault during ICO', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
var vaultAddress = await instance.vault.call();
let balance = await web3.eth.getBalance(vaultAddress);
assert.equal(balance.toNumber(), 1500000000000000000, 'ETH couldn\'t be transferred to the vault');
done();
});
});
it('Vault balance should be added to our wallet once ICO is over', function(done){
HashnodeCrowdsale.deployed().then(async function(instance) {
let balanceOfBeneficiary = await web3.eth.getBalance(accounts[9]);
balanceOfBeneficiary = balanceOfBeneficiary.toNumber();
var vaultAddress = await instance.vault.call();
let vaultBalance = await web3.eth.getBalance(vaultAddress);
await instance.finish(accounts[0], accounts[1], accounts[2]);
let newBalanceOfBeneficiary = await web3.eth.getBalance(accounts[9]);
newBalanceOfBeneficiary = newBalanceOfBeneficiary.toNumber();
assert.equal(newBalanceOfBeneficiary, balanceOfBeneficiary + vaultBalance.toNumber(), 'Vault balance couldn\'t be sent to the wallet');
done();
});
});
});
Each it()
block introduces a scenario and performs some actions on the contract to verify that the code is behaving as expected. I have written total 9 test cases. Feel free to think of more scenarios and write the corresponding tests.
Before running the tests we need to tell truffle that we have introduced a new contract called HashnodeCrowdsale
. Create a new file migrations/2_HashnodeCrowdsale.js
with the following content:
var HashnodeCrowdsale = artifacts.require("./HashnodeCrowdsale.sol");
module.exports = function(deployer) {
const startTime = Math.round((new Date(Date.now() - 86400000).getTime())/1000); // Yesterday
const endTime = Math.round((new Date().getTime() + (86400000 * 20))/1000); // Today + 20 days
deployer.deploy(HashnodeCrowdsale,
startTime,
endTime,
5,
"0x5AEDA56215b167893e80B4fE645BA6d5Bab767DE", // Replace this wallet address with the last one (10th account) from Ganache UI. This will be treated as the beneficiary address.
2000000000000000000, // 2 ETH
500000000000000000000 // 500 ETH
);
};
Now open up zeppelin-solidity/contracts/crowdsale/Crowdsale.sol
and comment out line number 44 where it says:
require(_startTime >= now);
We need to do the above modification so that we can set crowdsale start date to yesterday while deploying. Otherwise you have to provide a future date as the crowdsale start date which will be difficult to test.
The above code keeps our contract ready for deployment with the following settings:
- startTime is yesterday
- endTime is startTime + 20 days
- Current rate is 5 i.e. 1 ETH can buy 5 HTs
- 0x5AEDA56215b167893e80B4fE645BA6d5Bab767DE is the beneficiary wallet. You should replace this with the 10th wallet address (usually the last one) from Ganache UI. If you choose a different wallet from ganache, the last test case will fail! (Can you find out the reason? 😉 )
- Goal is 2 ETH and hard cap is 500 ETH
Now go to truffle.js
and paste the following:
module.exports = {
networks: {
development: {
host: "localhost",
port: 7545,
gas: 6500000,
network_id: "5777"
}
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
};
Ganache (installed earlier) is running on port 7545. The above code configures the local blockchain (Ganache) details and registers with truffle.
Now it's time to compile, deploy and test our code.
truffle compile
truffle migrate // deploy to local blockchain (Ganache)
To test, run the following command:
truffle test
Do note that truffle test
command automatically compiles and deploys your contracts before running the tests.
If the tests are successful, you should see something like this:
Congrats! You have verified your contract on your local machine.
Deploy to Ropsten TestNet
Now let's go one step ahead and deploy our code to Ropsten TestNet. In order to do that we need an ethereum wallet. For the sake of simplicity let's use MetaMask (also my personal favorite). So, go ahead and download the extension for your browser. Currently, it's available for Chrome, Firefox, Opera and Brave.
Once you have installed MetaMask, choose a password and note down the 12 word seed phrase.
Also, buy some test Ether by clicking on "Buy" button in MetaMask. It'll take you to a faucet where you can request some test ETH. We'll need this to pay our transaction fees while deploying the contract.
Now go ahead and paste the following code into truffle.js
:
var HDWalletProvider = require("truffle-hdwallet-provider");
var infura_apikey = "KbQuP7xkP1ZYNhJkUOXF"; // Either use this key or get yours at infura.io/signup. It's free.
var mnemonic = "<REPLACE THIS WITH YOUR METAMASK SEED PHRASES>";
module.exports = {
networks: {
development: {
host: "localhost",
port: 7545,
gas: 6500000,
network_id: "5777"
},
ropsten: {
provider: new HDWalletProvider(mnemonic, "ropsten.infura.io" + infura_apikey),
network_id: 3,
gas: 4500000
}
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
};
Replace the variable mnemonic
with the seed phrases obtained from MetaMask and run npm install
. We did this so that truffle chooses the first account created on MetaMask to deploy the contract.
If everything was installed correctly, run the following command to deploy your contract to Ropsten TestNet:
truffle migrate --network ropsten
It will take some time for the deployment to complete. Once it's over, you will see something like this:
Congrats! Your crowdsale is now live on TestNet. Note the contract address and Tx ref.
Testing
You can go ahead and test all the 9 scenarios on Ropsten. But for the sake of brevity, let's conclude this article with just 1 test which is: If I send 1 ETH to the Crowdsale Contract, I should get back 5 HTs.
So, open up MetaMask and send 1 ETH to the contract address. Once the transaction is complete (you can check the status on Etherscan), go to the tokens tab of MetaMask.
MetaMask doesn't show all your tokens by default. You have to add the specific token in order to see your balance. At this time we just have the crowdsale contract address. So, how do you get the ERC20 token address?
If you look at the contract code, you will see that there is a public variable token
defined in zeppelin-solidity/contracts/crowdsale/Crowdsale.sol';
. This is the address of your ERC20 token.
So, how do we read its value? It's simple. As the contract HashnodeCrowdsale
automatically deploys HashnodeToken
, you can find its address from the internal transactions tab on Etherscan.
Go to the following address:
https://ropsten.etherscan.io/tx/<txRef>#internal
Replace txRef
with the Tx Ref we noted earlier. Once the page loads click on the "To" address of the first row. It'll take you to a new page where you can see the Contract Address.
Note the address. Now go to MetaMask -> Tokens -> Add Token. Paste the token address. The symbol and decimals will be populated automatically. Hit Add and you are done!
If everything worked correctly, you should see 5 HTs in your account. In case you don't see it immediately after adding the token, refresh MetaMask. Sometimes MetaMask UI doesn't update after adding a new token.
Feel free to check all possible scenarios and let me know your findings in the comment section!
ICO Demo & Conclusion
I quickly put together some code to build a simple ICO page. Do check it out and read the source code to understand how to interact with your Smart Contracts through JavaScript.
If you spot any errors or inaccuracies, please report them in the comments - and as always feel free to ask any questions. Happy to help!
Roy Huang
Jan 29, 2018
One of the best out there! Hashnode will pass Stack in no time.
brian
Feb 9, 2018
Great write up, works perfectly. Well done! What is involved in getting this to mainnet, just changing the network?
Anwar Shaikh
Mar 12, 2018
Can you please have a tutorial on web3.js. Could not understand how to show the raised amount of eth.
ChiragMaliwal
Mar 13, 2018
I am also looking for the same.
Green Monster
Feb 1, 2018
Also, for anyone else trying out this tutorial but are using the latest version of zeppelin-solidity, I have found the following changes helpful.
(1) In HashnodeToken.sol change the filepath to include the ERC20 folder
import 'zeppelin-solidity/contracts/token/ERC20/MintableToken.sol';
(2) If you are unable to deploy the contract because of a gas issue, try updating the gas amount in truffle.js to reflect the limit in ganache. "gas: 6721974"
Hope this helps someone!
Geo
Jun 4, 2018
I still get the gas error when setting "gas: 6721974". It seems no matter what i try it will not work. Perhaps the error is misleading but I cant track down the cause.
Gael Durand
Aug 1, 2018
I've got the same problem as Geo. Any solution?
Green Monster
Feb 1, 2018
First of all, thank you very much for this tutorial! As a beginner, I found it very educational and fairly straightforward to follow. However, there is something that I am having trouble understanding.
Using the code provided, I am able to purchase 5HTs for 1ETH. Success!
This is utilizing "uint8 public decimals = 18;" in HashcodeToken.sol My understanding is that each HT is divisible up to 18 decimals.
From what I've read, setting uint8 "public decimals = 0; should create a token that is indivisible. However, deploying the contract with only this change creates far too many tokens!
So, it seems that in HashnodeCrowdsale.sol, the token distribution needs to be changed from: uint256 public maxTokens = 100000000000000000000;
to: uint256 public maxTokens = 100; for all token amounts in the contract. Is this a correct interpretation?
If so, I can't seem to work out is how the "rate" should change.
if (stage == CrowdsaleStage.PreICO) { setCurrentRate(5); } else if (stage == CrowdsaleStage.ICO) { setCurrentRate(2);
Setting the rate to 0.1 for example returns an error and keeping the rate "as is" creates errors when running the TestCrowdsale. I've tried looking at the documentation but I can't seem to wrap my mind around it! How can I make this HashnodeToken indivisible and what effect does it have on the rate?
Meshach Ishaya
Mar 16, 2018
I am getting this error when I run "truffle deploy/migrate"
Deploying HashnodeCrowdsale... ... 0x53ddcb6df7447ea1a73b23e0ca5059012c996e92871384289b5ba1aaec11f02f Error encountered, bailing. Network state unknown. Review successful transactions manually. Error: The contract code couldn't be stored, please check your gas amount. at Object.callback (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/contract.js:147:1) at /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/method.js:142:1 at /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/requestmanager.js:89:1 at /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-migrate/index.js:225:1 at /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-provider/wrapper.js:134:1 at XMLHttpRequest.request.onreadystatechange (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/httpprovider.js:128:1) at XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:64:1) at XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1) at XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1) at IncomingMessage.<anonymous> (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:469:1) at IncomingMessage.emit (events.js:165:20) at endReadableNT (_stream_readable.js:1101:12) at process._tickCallback (internal/process/next_tick.js:152:19)
It keeps failing on my machine..
P.S - I have checked my gas limit in truffle.js, increased it and checked for anything I could find pointing this error but none so far.
Hodl Coin
Mar 22, 2018
Any luck with this? I have the same issue and can't figure out what's wrong.
Martin Blanco
Mar 2, 2018
Now... Once the ico is finished, how can I see the balance of tokens that have been returned to the addresses that have participated?
web3.eth.getBalance(accounts[0]);
Shows me the eth balance, not the token balance.
Martin Blanco
Mar 6, 2018
I was looking and analyzing, to see the tokens you must make the query with the api webjs of ethereum making the call with your JSON ABI contract.
Load webjs api on html file first:
<script src="web3.min.js"></script>
then execute this JS example:
var web3 = new Web3(new Web3.providers.HttpProvider("RPCBlockchainIP:RPCBlockchainPort")); var erc20Abi = [Your JSON ABI Contract]; var tokenContract = web3.eth.contract(erc20Abi).at("Your Contract Token Address"); var tokenBalance = tokenContract.balanceOf.call("Your Account Address to Test"); console.log(tokenBalance);
Mykolas Malaiška
Mar 7, 2018
Hello,
An error occurred while testing
Contract: HashnodeCrowdsale ✓ should deploy the token and store the address (97ms) ✓ should set stage to PreICO (109ms) 1) one ETH should buy 5 Hashnode Tokens in PreICO
Events emitted during test: --------------------------- Mint(to: <indexed>, amount: 5000000000000000000) Transfer(from: <indexed>, to: <indexed>, value: 5000000000000000000) TokenPurchase(purchaser: <indexed>, beneficiary: <indexed>, value: 1000000000000000000, amount: 5000000000000000000) EthTransferred(text: forwarding funds to wallet) --------------------------- ✓ should transfer the ETH to wallet immediately in Pre ICO (572ms) ✓ should set variable `totalWeiRaisedDuringPreICO` correctly (41ms) ✓ should set stage to ICO (103ms) 2) one ETH should buy 2 Hashnode Tokens in ICO Events emitted during test: --------------------------- Mint(to: <indexed>, amount: 3000000000000000000) Transfer(from: <indexed>, to: <indexed>, value: 3000000000000000000) TokenPurchase(purchaser: <indexed>, beneficiary: <indexed>, value: 1500000000000000000, amount: 3000000000000000000) EthTransferred(text: forwarding funds to refundable vault) --------------------------- ✓ should transfer the raised ETH to RefundVault during ICO (265ms) ✓ Vault balance should be added to our wallet once ICO is over (767ms)
7 passing (3s) 2 failing
1) Contract: HashnodeCrowdsale one ETH should buy 5 Hashnode Tokens in PreICO: Uncaught ReferenceError: HashnodeToken is not defined at test/TestCrowdsale.js:26:32 at <anonymous> at process._tickCallback (internal/process/next_tick.js:188:7)
2) Contract: HashnodeCrowdsale one ETH should buy 2 Hashnode Tokens in ICO: Uncaught ReferenceError: HashnodeToken is not defined at test/TestCrowdsale.js:69:32 at <anonymous> at process._tickCallback (internal/process/next_tick.js:188:7)
Soubhik Ghorai
Apr 3, 2018
Any luck with this? I have the same issue and can't figure out what's wrong.
Tony
Feb 28, 2018
Thank you Sandeep.
I found a little bit issue where after i bought every token in the PreICO, and start to buy the token in ICO, I couldn't get the token into my account. Does anyone know why
Martin Blanco
Mar 6, 2018
that is not written in the contract code, you must change the status of the contract manually by calling the function: "setCrowdsaleStage" to 1, using web3js and using owner account in metamask
Shubham Constacloud
Mar 8, 2018
Hello Martin Blanco, can you please explain in details how to call contract methods in ropsten test network. I have successfully done the PreICO but now i am stuck in start the ICO.
Martin Blanco
Mar 6, 2018
I would like the tokens to be distributed at the end of the ico, how can we do this?
Vamsee
Mar 25, 2018
Hi, trying to add a transfer test like this but it is failing.
it('Token transfer', async () => { const contractInstance = await HashnodeCrowdsale.deployed() let ecoSystemBalance = await web3.eth.getBalance(accounts[9])
const tokenAddress = await contractInstance.token.call() const hashToken = HashnodeToken.at(tokenAddress) const owner = await hashToken.owner.call() console.log(owner) await hashToken.approve(accounts[4], 4) console.log(await hashToken.allowance(owner, accounts[4])) await hashToken.transferFrom(owner, accounts[4], 3) let receiverBalance = await web3.eth.getBalance(accounts[4]) receiverBalance = receiverBalance.toNumber() assert.equal(receiverBalance, 3, 'Transfer failed')
})
getting this error. Any idea how to get that working? Tried with other from addresses like accounts[9] but getting the same error.
== output of the test run ===
0x85d22c38d2b791a5803fc3f15267fda1b0473c3b BigNumber { s: 1, e: 0, c: [ 0 ] }
Events emitted during test: --------------------------- Approval(owner: <indexed>, spender: <indexed>, value: 4) ---------------------------
Error: VM Exception while processing transaction: revert at Object.InvalidResponse (node_modules/truffle-contract/node_modules/web3/lib/web3/errors.js:38:16) at node_modules/truffle-contract/node_modules/web3/lib/web3/requestmanager.js:86:36 at node_modules/truffle-provider/wrapper.js:134:9 at XMLHttpRequest.request.onreadystatechange (node_modules/truffle-provider/node_modules/web3/lib/web3/httpprovider.js:128:7) at XMLHttpRequestEventTarget.dispatchEvent (node_modules/xhr2/lib/xhr2.js:64:18) at XMLHttpRequest._setReadyState (node_modules/xhr2/lib/xhr2.js:354:12) at XMLHttpRequest._onHttpResponseEnd (node_modules/xhr2/lib/xhr2.js:509:12) at IncomingMessage.<anonymous> (node_modules/xhr2/lib/xhr2.js:469:24) at endReadableNT (_stream_readable.js:1055:12) at _combinedTickCallback (internal/process/next_tick.js:138:11) at process._tickCallback (internal/process/next_tick.js:180:9)
Vamsee
Mar 26, 2018
Here is the fixed/working code, thanks to advice from github.com/OpenZeppelin/zeppelin-solidity/…
it('Token transfer', async () => { const fromAccount = accounts[1] const toAccount = accounts[4] const transferLimit = 4000000000000000000 const transferAmount = 3000000000000000000 let ecoSystemBalance = await tokenInstance.balanceOf(fromAccount) assert.isAtLeast(ecoSystemBalance, transferLimit, 'From account balance should be atleast 4') await tokenInstance.approve(toAccount, transferLimit, {from: fromAccount}) assert.equal(await tokenInstance.allowance(fromAccount, toAccount), transferLimit, 'Allowance from->to should be 4') console.log(`Attempting a transfer with ecoSystem balance of ${ecoSystemBalance}`) await tokenInstance.transferFrom(fromAccount, toAccount, transferAmount, {from: toAccount}) assert.equal(await tokenInstance.balanceOf(toAccount), transferAmount, 'To account should have 3') })
ChiragMaliwal
May 7, 2018
Hello thanks for the good article.
uint256 unsoldTokens = totalTokensForSale - alreadyMinted; if (unsoldTokens > 0) { tokensForEcosystem = tokensForEcosystem + unsoldTokens; }
Just because of these 3 lines I am getting error :
could potentially lead to re-entrancy vulnerability. <br/><i>Note:</i> Modifiers are currently not considered by this static analysis.
KC Tam
Apr 23, 2018
Hi Sandeep, Great work! Based on your work I have used geth to inspect what happens inside the ethereum network when executing the testing items. Here I documented the whole process and hope to serve as a companion guide to your great work. You can find it here . cheers, kc
Olga Miller
entrepreneur
Dec 12, 2018
Try to deploy your crowdsale smart contract on the Ethereum blockchain through mywish.io
Solomon Ezeabasili
crypto enthusiate
Apr 11, 2019
I tried this on ropsten testnet. Everything worked fine.... Then i deployed on mainnet , the tokens generated were invalid. the smart contract is invalid, lost etherum used in creating on mainnet.
victor perez
productor
Jun 9, 2019
hola como construimos la frond end para la ICO
David Alvarado
Feb 7, 2018
Everything works great, thank you.. my question is once you deploy to Ropsten test network how do you go about interacting with the contract to call finish() ?
What are the ways to interact with the contract once it's on Ropsten?
Thanks.
Sandeep Panda
Feb 19, 2018
Hi David! Thanks for reading. Once you have the contract address you can interact with the contract using online Remix compiler or Mist.
David Alvarado
Feb 19, 2018
Hi Sandeep.. thanks for your response.. I have been able to access the contract via Mist and when I try to run the "finish" function it won't execute do to "gas limit" it seems to be requiring excessive amount and when I adjust down, it fails.. any ideas how to get around this or resolve this?.. Thank you again.
Roman S
Mar 2, 2018
How to manage ICO stage after contract is deployed?
Where can I find examples of using setCrowdsaleStage() and finish() functions?
Tran Tho
Developer
Mar 12, 2018
Hi gys After I Deploy to Ropsten TestNet Success, then I go to MetaMask -> Tokens -> Add Token. Paste the token address, but 0 in my Token :( This TX ropsten.etherscan.io/tx/0xa85ba8a43a7cb7a9…
Tony
Mar 13, 2018
0x8F03b85206128395D156Eadbd9cE8eDBd0cc537D this should be your token address
Tran Tho
Mar 13, 2018
New Transaction deployed but my owner banlance = 0 and nobody can send ETH to this transaction :( ropsten.etherscan.io/address/0x6a71265726c…
ChiragMaliwal
Mar 13, 2018
How can we get the abi of this contract ?
Shubham Constacloud
Mar 16, 2018
You can get your contract abi after verify from etherscan by passing your contract address,contract name,compilter version,your contracts complete source code and bytcode but i already tried and got some error. i got my abi from contract json file into build directory.
ka eliz
Mar 2, 2018
Hi Sandeep, thanks for your great post.
I am having problems when I try to test on Ropsten. I successfully passed Truffle test; but on
truffle migrate --network ropsten
I got this:
Running migration: 1_initial_migration.js
Deploying Migrations...
Error encountered, bailing. Network state unknown. Review successful transactions manually.
insufficient funds for gas * price + value
I Tried to change gas value many times, but nothing happened.
Could you help me?
thanks
Abe Rios
Mar 12, 2018
I'm having the same issue deploying to TestNet. I get the message "The contract code couldn't be stored, please check your gas amount". Increasing gas doesn't seem to work, and if I increase it too much, exceed the max gas limit. Anyone find a solution this?
Shubham Constacloud
Mar 16, 2018
i figured out this issue by passing gas 4000000 and before deploy your contract please get some ether balance in your owner account using metamask ropsten test faucet, after that you will be to deploy contract without any error.
ChiragMaliwal
Mar 8, 2018
Please have a look on this issue : ethereum.stackexchange.com/questions/42122…
Getting error : browser/mycontract.sol:64:27: DeclarationError: Identifier not found or not unique. contract StandardToken is ERC20, BasicToken { ^---^
browser/mycontract.sol:64:34: DeclarationError: Identifier not found or not unique. contract StandardToken is ERC20, BasicToken { ^---^
Nilanjan Maity
Apr 27, 2018
Hello Sandeep Panda, first of all, thanks for writing this awesome article. This really helps. I followed everything and it worked all fine until when I added the token in meta mask it is showing me 0 nT (nT is the symbol I updated).
What might I be doing wrong?
John Christian
Mar 28, 2018
Sandeep - any chance you'd be willing to write a similar article or ebook on how to do these exact things using Stellar Smart Contracts?
Sandeep Panda
Mar 28, 2018
Hi.. Check out tokenlauncher.co. I built this to show how the same setup can be achieved using Stellar.
Daniel Tung
Apr 5, 2018
I would like to add the following token distribution mechanism: to let each user get extra tokens proportional to the total number of tokens sold, i.e. the more tokens are sold in total, the more extra tokens each user will receive. How to do this in the codes? Thanks.
David Alvarado
Feb 21, 2018
Does anyone else have an issue calling the "finish()" function? When I try to invoke it I get a "gas limit too high" error.. no matter what I try to set it at it won't accept, it fails... have tried using Mist to access the contract after it's deployed to Ropsen
David Alvarado
Mar 2, 2018
Yes.. I've included those when calling the function, but it fails do to Gas limit no matter what I set it at
Tony
Mar 13, 2018
You have to use the creator account/address, in order to call the function
ChiragMaliwal
Mar 8, 2018
Is necessary to use metamask with infura ?
Aleš Uršej
Jan 30, 2018
Hello. Thank you for great "howto".
I have a problem when I try to compile. I get: Error parsing D:\crypto\coin\contracts\HashnodeCrowdsale.sol: ParsedContract.sol:1:1: ParserError: Expected pragma, import directive or contract/interface/library definition. �p r a g m a s o l i d i t y ^ 0 . 4 . 1 8 ; ^ Compilation failed. See above.
What could be the problem?
Thanks,
Ales
Aleš Uršej
Feb 15, 2018
Hey. Did you look it? :)
Vamsee
Feb 28, 2018
Hi Sandeep, thanks for publishing this. Just FYI you can simplify the tests like this:
it('should deploy the token and store the address', async () => { const instance = await HashnodeCrowdsale.deployed() const token = await instance.token.call() assert(token, 'Token address couldn\'t be stored') });
Igor Yalovoy
Aug 2, 2018
if ((stage == CrowdsaleStage.PreICO) && (token.totalSupply() + tokensThatWillBeMintedAfterPurchase > totalTokensForSaleDuringPreICO)) { msg.sender.transfer(msg.value); // Refund them EthRefunded("PreICO Limit Hit"); return; }
It is better to user
revert
here?Deactivated User
May 25, 2018
I've got this error that I can't figure out:
...also on this line:
function HashnodeCrowdsale(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet, uint256 _goal, uint256 _cap) CappedCrowdsale(_cap) FinalizableCrowdsale() RefundableCrowdsale(_goal) Crowdsale(_startTime, _endTime, _rate, _wallet)
Crowdsale want 3 arguments, not 4, which I get but how is your code able to work?
Angga Andrean
Jul 7, 2018
would you share your version truffle, zeppelin ?? or anybody here can help me ?
$ truffle compile Compiling .\contracts\ElectroLightCrowdsale.sol... Compiling .\contracts\ElectroLightToken.sol... Compiling .\contracts\Migrations.sol... Compiling zeppelin-solidity/contracts/crowdsale/CappedCrowdsale.sol... Compiling zeppelin-solidity/contracts/crowdsale/RefundableCrowdsale.sol... Compiling zeppelin-solidity/contracts/token/MintableToken.sol... Compiling zeppelin-solidity\contracts\crowdsale\Crowdsale.sol... Compiling zeppelin-solidity\contracts\crowdsale\FinalizableCrowdsale.sol... Compiling zeppelin-solidity\contracts\crowdsale\RefundVault.sol... Compiling zeppelin-solidity\contracts\math\SafeMath.sol... Compiling zeppelin-solidity\contracts\ownership\Ownable.sol... Compiling zeppelin-solidity\contracts\token\BasicToken.sol... Compiling zeppelin-solidity\contracts\token\ERC20.sol... Compiling zeppelin-solidity\contracts\token\ERC20Basic.sol... Compiling zeppelin-solidity\contracts\token\MintableToken.sol... Compiling zeppelin-solidity\contracts\token\StandardToken.sol... zeppelin-solidity/contracts/crowdsale/CappedCrowdsale.sol:22:37: ParserError: Expected token LBrace got 'View' function validPurchase() internal view returns (bool) { ^ ,zeppelin-solidity/contracts/crowdsale/Crowdsale.sol:94:37: ParserError: Expected token LBrace got 'View' function validPurchase() internal view returns (bool) { ^ ,zeppelin-solidity/contracts/crowdsale/RefundableCrowdsale.sol:56:33: ParserError: Expected token LBrace got 'View' function goalReached() public view returns (bool) { ^ ,zeppelin-solidity/contracts/math/SafeMath.sol:9:47: ParserError: Expected token LBrace got reserved keyword 'Pure' function mul(uint256 a, uint256 b) internal pure returns (uint256) { ^ ,zeppelin-solidity/contracts/token/BasicToken.sol:38:45: ParserError: Expected token LBrace got 'View' function balanceOf(address _owner) public view returns (uint256 balance) { ^ ,zeppelin-solidity/contracts/token/ERC20.sol:12:61: ParserError: Expected token LBrace got 'View' function allowance(address owner, address spender) public view returns (uint256); ^ ,zeppelin-solidity/contracts/token/ERC20Basic.sol:11:42: ParserError: Expected token LBrace got 'View' function balanceOf(address who) public view returns (uint256); ^ ,zeppelin-solidity/contracts/token/StandardToken.sol:60:63: ParserError: Expected token LBrace got 'View' function allowance(address _owner, address _spender) public view returns (uint256) { ^ Compiliation failed. See above.