How to write a smart contract for ICO in 5 minutes

Hello! In this article I will tell you how in 5 minutes and a few commands in the terminal to launch a smart contract to collect money for your ICO on Ethereum. This essay will potentially save you tens of thousands of US dollars, since any programmer — and not a programmer too — will be able to run an audited and secure smart contract (instead of paying $ 15,000 - $ 75,000 for development). In short, you can send money to this smart contract and get ERC20 tokens for it. You can say this article is a collection of all the experience I gained by launching an ICO for my project.
On the Internet, these are yours and so full of an article about smart contracts, but as soon as you start to write this, you are faced with the fact that the information is repeated everywhere, and the tutorials on how to run your ERC20 are either not there, or they are outdated and that is utterly impossible. By the way, in order for this article to remain relevant, I will try to indicate potential places where it might become outdated (and how to fix it). Go!
Solidity
This is the name of the main language that the kefir team has developed to launch smart contracts. If you are a programmer, then just glance over the documentation of the language - it is indecently simple. By the way, they made it simple so that it was more difficult to make a mistake in writing a smart contract. So absolutely any programmer at least at the junior level will be able to sort it out. Absolutely no sense in paying huge money to developers who know solidity - it will be cheaper to train an existing developer.
Smart contracts
... and everything you need to know about them. Skip this section if you are not a programmer. A smart contract is a piece of code. In principle, this is a class in solidity (OOP, yes), which has two types of functions: state changing and non changing. Well, to launch functions in a smart contract just by sending kefir to it, you need to mark this function
payable
. The state is a data warehouse, blockchain, epta. Contracts can change the blockchain (state, storage) - but to change the blockchain you need to pay kefir to miners. How they will share kefir will not be analyzed in this article. Paying miners for running a code that changes state is called Gas. If someone from the outside throws kefir on the address of a smart contract with a function call, marked
payable
, but not marked Constant
, View
orPure
, then the required amount of kefir will be deducted from the amount sent to pay for the miners. Usually in ERC20 tokens are functions that either issue token senders as kefir, or transfer tokens from one token holder to another. And if you mark a function in the contract with the words
Constant
or View
(they mean the same thing, they allow only to read the state), or Pure
(the same, only the state does not read), then even kefir will not have to spend on the execution of this function! I will say even more, these functions do not need to be called up by transactions - after all, any kefir client, theoretically, can execute it at home - and no one else needs to know about it (because nothing is written in the blockchain).And there are two important things in solidity: multiple inheritance and function modifiers. About them, too, need to know.
The first - a contract can be inherited along with several classes such as
TimedCrowdsale
, CappedCrowdsale
, MintedCrowdsale
, Ownable
- with the constructor function is also run one after the other - but it is, I will explain the example already on. The second is the ability to create functions that will then be inserted into other functions. It's like simple encapsulation, just a bit more flexible — it's literally a function template . When you create a modifier, you write a special character.
_
where you mean the code of the function using this modifier. That is, modifiers are not just an encapsulated functional that returns a value; this is the function template when the code from the modifier is literally inserted into the function using this modifier. Let's go to practice.
Cooking environment
If you do not know what Terminal is - read this article here . If you are on the windows, set yourself a terminal through the WLS. If you are already familiar with the Terminal, continue. Also, immediately put yourself Node.js - it will be necessary for the next steps. It is better to set LTS, but, in fact, absolutely no difference, which of the modern versions of the node to put.
The first thing we will immediately install and start the process of synchronizing the blocks is this
geth
. In short, this is a utility written in Go that will allow us to launch the ether node on the local computer and connect to the test and real network. You can install via installers , but I strongly advise you to start writing geth
directly to the Terminal, as described here. Check whether you have established the rules geth
, you can run the command in the Terminal:geth version
If you get the version of geth - everything is in openwork, we continue the tutorial. If not - bad, correct; It seems you will have to deal with love caress with the Terminal and your operating system - but you will not be the first to figure it out. How to install geth, run the following command in the Terminal:
geth --testnet console
This will start the process of synchronization of your node with the test server, the blocks of which can be viewed here . You can check whether you synchronized with the network in the console by
geth
writing:eth.blockNumber # если 0 — то еще не синхронизировались
eth.syncing # выплюнет прогресс синхронизации или false, если ничего не происходит
The synchronization process took me from 1 to 4 hours - when how. Also, in addition to block synchronization, you will have to wait for state synchronization - this is more often longer than block synchronization. You can also use commands
geth
with a flag --light
— then synchronization lasts from a few seconds to a minute and you can still deploy contracts. All right, we have put the first utility - we put the following. We need to put an analog
geth
, only a very local simulation of the blockchain - testrpc
. Yes, yes, we have 3 blockchains :testrpc
- local blockchain simulation; fast but unrealistic and stored only on your cargeth --testnet
- already a real blockchain, but the test network, where you can get kefir for free and test any obscenities, won't lose moneygeth
- Mainnet, main, real blockchain, real kefir; all grown-up, mistakes here - the loss of real kefir
Accordingly, we will start the test of contracts with
testrpc
, then we will press in geth --testnet
, and then we will start right in geth
. We install
testrpc
by running the following command:npm install -g ethereumjs-testrpc
Well, or get up immediately with a truffle, as now
testrpc
under the wing of the truffle and called ganache-cli
. Although the devil knows, everything testrpc
worked well for me with vanilla . And if it works, don't touch it, as they taught me at the intergalactic academy. You can also start it up and check the installation by writing it truffle
to the console, but we are already synchronizing the test blockchain - we will not disturb it. Well, have you figured out the blockchains? Now there is a node and the test is even synchronized? We put a convenient utility for working with smart contracts on kefir -
truffle
, the following command:npm install -g truffle
truffle version # сразу чекнем, установился ли, проверив версию
A truffle is a tool that allows you to keep smart contracts in different files, import other files, and also compiles your smart contract code into one big byte code (unreadable by human), automatically finds your locally running
geth
(test and real) Or testrpc
, deploy your smart contract to this network. Also, it checks your smart contract code for errors and, more recently, completed transactions help debug . Mast of, shorter. At this stage, you should be set:
testrpc
, geth
, truffle
- if something of this version is not present or do not spit into the console on request, that is correct; otherwise you will not succeed.Also, I nakidal simple bash-skriptik , which installs everything for you. Called like this:
source <(curl -s https://raw.githubusercontent.com/backmeupplz/eth-installer/master/install.sh)
- but I have never tested it yet, so I'm not sure about its efficiency. However, pull requests will be only happy.
Figa contract
Everything has been thought out and written for you - this is good. A little smut will not care - but I will try to minimize it to you. We will use ready-made ERC20 contracts from OpenZeppelin - this is now the industry standard, they have been audited, and indeed all their code is used. Thanks to them so much for their contribution to the open-sence
Make
cd
some safe folder and then write:mkdir contract && cd contract
In this folder we will work. Create a stub here for our smart contract:
truffle init
Be hurt, clearly. We now have two very important folders in which we will climb:
contracts
and migrations
. The first is the code of our contracts, the second is the code for truffle, so that we know what to do when the contracts are in blockchain. Next we need to pick up the current smart contract code from npm and, strictly speaking, start the project itself:
npm init -y # создадим проект без вопросов (флаг -y)
npm install -E openzeppelin-solidity # заберем контракты и зафиксируем текущую версию (флаг -E)
Great, the smart contract code from OpenZeppelin is in our pocket in the folder
node_modules/openzeppelin-solidity/contracts
. Now we go to the main folder contracts
, delete all the files there and add the files MyToken.sol
and MyCrowdsale.sol
- naturally, you will call your contracts differently. The first will be the contract for our ERC20 Token, and the second - by the contract of our ICO, which will accept kefir and distribute to people MyToken
. This article may be out of date, but you can always see how OpenZeppelin suggests that you create contracts in their repository . This is how it will look like MyToken.sol
:pragma solidity ^0.4.23;
// Importsimport"../node_modules/openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol";
// Main token smart contract
contract MyToken is MintableToken {
string public constant name = "My Token";
string public constant symbol = "MTKN";
uint8 public constant decimals = 18;
}
Nice - you have a smart contract of your own token (just change the names in the constants)! You can see that there is inheritance from
MintableToken
- but everything is as simple as possible. This is a token that can be released (from the English. "Mint" - minted), and only the owner has the right to release it, since it MintableToken
is also inherited from Ownable
. Also, MintableToken
it is also inherited from the ERC20 tokens classes written by OpenZeppelin, in which the ERC20 interface is implemented:contract ERC20Basic {
functiontotalSupply() publicviewreturns (uint256);
functionbalanceOf(address who) publicviewreturns (uint256);
functiontransfer(address to, uint256 value) publicreturns (bool);
eventTransfer(address indexed from, address indexed to, uint256 value);
}
Yeah, here's the whole ERC20 interface. Complicated? I do not think. It gives an opportunity to see how many tokens were issued, check the address balance and transfer tokens to another address by spitting a translation event for kefir light clients into the network. And all this you get a
MyToken.sol
head start in your thanks to the work of OpenZeppelin - they are great. And now let's move on to the main part of our ICO - we need to take yogurt and distribute
MyToken
! This is what yours will look like MyCrowdsale.sol
:pragma solidity ^0.4.23;
// Importsimport"../node_modules/openzeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol";
import"../node_modules/openzeppelin-solidity/contracts/crowdsale/distribution/RefundableCrowdsale.sol";
import"../node_modules/openzeppelin-solidity/contracts/crowdsale/validation/CappedCrowdsale.sol";
import"../node_modules/openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol";
contract MyCrowdsale is CappedCrowdsale, RefundableCrowdsale, MintedCrowdsale {
constructor(
uint256 _openingTime,
uint256 _closingTime,
uint256 _rate,
address _wallet,
uint256 _cap,
MintableToken _token,
uint256 _goal
)
public
Crowdsale(_rate, _wallet, _token)
CappedCrowdsale(_cap)
TimedCrowdsale(_openingTime, _closingTime)
RefundableCrowdsale(_goal)
{
// Это тут просто, чтобы показать, что можно сделать// Проверяем, что софткеп ниже хардкепаrequire(_goal <= _cap);
}
}
So, so, so, what is here with us? What, guys, smart contracts? Our public sale of tokens inherits three of the most popular properties: it has a hard cap, more than which it will not work; soft cap, without having collected which ethers come back; the time to start and end the sale of tokens. In fact, what else is needed for happiness?
Programmers notice how the constructors of classes of multiple inheritance are lined up and get arguments from the main constructor
MyCrowdsale
. Also, we check that our hardkep is higher than softkep - ales gut! Also, do not be intimidated by the parameters cloud in the constructor MyCrowdsale
- we will pass them on at the stage of contract deployment in truffle.That's all - you have ready-made contracts of your own ERC20 token and even an ICO smart contract that is customized according to your wishes and distributes your tokens for kefir. Also, it is supported by all ERC20 wallets - lyapota! We turn to the manual tests and Deploy.
Migrations
As I said earlier, we will be testing on three blockchain networks consistently, but the testing process will always be the same. Let's start with
testrpc
, then move on to geth --testnet
and on in geth
. Sow, we just wrote the code, let's try to compile it. In the project folder, type:truffle compile
If everything compiles without problems, then you will have a daddy
build
, which will contain krakozyabra truffle, so that he could zaplolit byte code of your smart contracts in the blockchain. Before deploying smart contracts, we need to tell the truffle what to do. The smart contract contract in a truffle is called migration - well, well, let's stick to this terminology. Log in migrations/1_initial_migration.js
and change it as follows:const token = artifacts.require("../contracts/MyToken.sol");
const crowdsale = artifacts.require("../contracts/MyCrowdsale.sol");
module.exports = function(deployer, network, accounts) {
const openingTime = 1514764800; // 15 Июня 2018const closingTime = 1561939200; // 1 Июля 2019const rate = new web3.BigNumber(1); // 1 токен за 1 эфирconst wallet = '0x281055afc982d96fab65b3a49cac8b878184cb16'; // Кошелек-бенефициарconst cap = 200 * 1000000; // Хардкепconst goal = 100 * 1000000; // Софткепreturn deployer
.then(() => {
return deployer.deploy(token);
})
.then(() => {
return deployer.deploy(
crowdsale,
openingTime,
closingTime,
rate,
wallet,
cap,
token.address,
goal
);
})
.then(() => {
// Crowdsale должен владеть токеномvar tokenContract = web3.eth.contract(token.abi).at(token.address);
web3.eth.defaultAccount = web3.eth.accounts[0];
tokenContract.transferOwnership(crowdsale.address);
});
};
This is the file that truffles will use to deploy contracts. So what are we doing with this? First, we requested compiled
MyToken
and MyCrowdsale
. After, we set constants with all the arguments of our ICO - set the start and end time; how many tokens people will receive for 1 wei kefir (0.000000000000000001 eth = 1 wei; setting decimals
indicates how many wei orders are needed to get 1 of your new token); a purse where kefirs received on sale will come; hard-cap and soft-cap. Note that openingTime
there should always be after the time of the current block in the blockchain - otherwise your smart contract will not fail due to the condition check inTimedCrowdsale
. I attacked this rake, but failed transactions can not be debugged at all. Change these constants on your own. The next step is to deploy smart contracts. There is nothing interesting here: we have an object
deployer
that deploit the artifacts of smart contracts and passes the arguments there. Notice that at first deploitsya MyToken
, and only then MyCrowdsale
- and in the second the address of the first is transferred by argument. Then the most interesting thing is what they do not write about either in the documentation or in books. When you create from a wallet
MyToken
, this wallet becomes the owner MyToken
of the superclass Ownable
- the same thing happens with MyCrowdsale
. If you dig deep into it MintableToken
, you can see that it is only possible to mint coins Owner
! And who is the ownerMyToken
? That's right: the address that zadloplo it. And who will send requests for minting coins? That's right: a smart contract MyCrowdsale
. Let me remind you that the address that created MyToken
and the address MyCrowdsale
are two different addresses. Therefore, we add a non-Orthodox third step of deployment, where the address that secured the contracts (
web3.eth.accounts[0]
) calls the function transferOwnership
on the contract MyToken
so that it MyCrowdsale
owns MyToken
and can mint coins. And MyCrowdsale
still under possession web3.eth.accounts[0]
- so everything is a bundle.Noteweb3.eth.accounts[0]
: when deploying a smart contract, make sure that geth or testrpc have the correct wallet inweb3.eth.accounts[0]
- do not lose the private key to it, even though it doesn’t hurt you, but suddenly the owner will have to do something later, and the key is gone?
Intestrpc
, as a rule, accounts are created immediately upon startup and they are immediately unlocked; however, it’s worth creating an account on the test and real blockchain of the air throughpersonal.newAccount()
- continue to replenish this address through the Faucet on the test blockchain or real kefir on the real blockchain. Do not lose your password and private keys.
Also, you can add an existing wallet to your accounts by callingweb3.personal.importRawKey('pvt_key', 'password')
, but for this you need to callgeth
with an additional parameter--rpcapi="db,eth,net,web3,personal,web3"
. I think you figure it out.
Testing and Deploy
Yes, the contracts are ready, the migrations are written, it remains only to enclose and check. Both
geth
(test and real) are testrpc
managed the same way through truffle console
- so I testrpc
’ll describe the verification method for and just tell you how to turn on geth
after. And so, we launch a test local kefir blockchain:testrpc
Um ... that's it. You have a local simulation of the kefir blockchain.
And in order to block the test blockchain of ether, you will do it instead of this commandgeth --testnet --rpc
. And in order to block the real blockchain of ether, you can write it simplygeth --rpc
. The flag is--rpc
needed so that the truffle can connect. The following steps of deployment and test are more or less the same for all three types of blockchain. The only thing is that after you start the test or real blockchain throughgeth
, it will start synchronizing the blocks - and this can take up to 4-5 hours on a good Internet connection. A remark about this one was at the very beginning of the article. Before deploying smart contracts, I recommend waiting for full synchronization. Also, the blockchain weighs around 60-100 gigabytes, so prepare disk space for this.
Also-also, make sure it isweb3.eth.accounts[0]
unlocked. You can usually register in the consoletestrpc
, which opens immediately, or in a separate window of the Terminal in the console, which opens throughgeth console
:eth.unlockAccount(eth.accounts[0], "Пароль, полученный при создании учетки", 24*3600)
- this will unlock your account, which should create a smart contract
Now we open a new Terminal window (we
testrpc
don’t close it - it should work) and write it in the project folder:truffle migrate --reset
This magic command will compile a smart contract (that is, no need to write each time
truffle compile
) and deposit it on the micro-server of the blockchain found locally open. It is worth noting that if you testrpc
do it instantly, then the test and real blockchains will include the transaction in the following blocks much longer. After that, you should spit out something similar in the console:Using network 'development'.
Running migration: 1_initial_migration.js
Running step...
Replacing MyToken...
... 0x86a7090b0a279f8befc95b38fa8bee6918df30928dda0a3c48416454e2082b65
MyToken: 0x2dc35f255e56f06bd2935f5a49a0033548d85477
Replacing MyCrowdsale...
... 0xf0aab5d550f363478ac426dc2aff570302a576282c6c2c4e91205a7a3dea5d72
MyCrowdsale: 0xaac611907f12d5ebe89648d6459c1c81eca78151
... 0x459303aa0b79be2dc2c8041dd48493f2d0e109fac19588f50c0ac664f34c7e30
Saving artifacts...
I think you already understood that the console gave you the addresses of smart contracts
MyToken
and MyCrowdsale
. Everything! The smart contract is secured on the blockchain, whose micro-server is open. It remains only to verify that the tokens are indeed distributed to users who send kefir on a smart contract MyCrowdsale
. We register the following in the Terminal to enter the truffle console:truffle console
We register the following in the now truffle (no comment only):
// Сохраняем адреса смарт-контрактов
t="0x2dc35f255e56f06bd2935f5a49a0033548d85477"// Замените на адрес своего MyToken
с="0xaac611907f12d5ebe89648d6459c1c81eca78151"// Замените на адрес своего MyCrowdsale// Получаем инстансы смарт-контрактов
token=MyToken.at(t)
crowdsale=MyCrowdsale.at(c)
// Сохраним аккаунт в более короткое имя
account=web3.eth.accounts[0]
// Проверяем, сколько токенов у нашего аккаунта
token.balanceOf(account) // должно быть 0// Отправляем кефира на смарт-контракт
web3.eth.sendTransaction({from: account, to:c, value: web3.toWei(0.1, 'ether'), gas: 900000})
In the case of,
testrpc
you can immediately check the balance of our wallet again, but in the case of the test and real blockchain, you need to wait until our transaction is included in the block - usually, when this happens, the truffle gives you the transaction number. Wait? Check again our balance in MyToken
:// Проверяем, сколько токенов у нашего аккаунта
token.balanceOf(account) // должно быть больше нуля
That's all! First test your contract for
testrpc
, then for geth --testnet
, then deploy for geth
. So you launched your own ICO! And you did not have to spend dozens of kilobaksov for auditing and launching. Nakosyachit so that we provided the guys from OpenZeppelin, in fact, very difficult. And when you use it truffle
, so the development on solidity generally turns into a fairy tale. Well, except when transactions are reversed even during execution on a smart contract - debug them to hell. But debugging smart contracts is truly worthy of a separate article.Conclusion
Thank you so much for reading to the end of this article! If I managed to save you time or money, or if you learned something new from this article, then I will be very happy. I would also be very grateful if you share this article with your friends or acquaintances who want to conduct an ICO - save them $ 75,000 for under-programmers who suck out money from the crypto market, like parasites, copy-and-paste the same 25 lines of code .
Good luck in developing smart contracts! Any questions? You are welcome in the comments - I will answer with pleasure and will try to help with the problems.
Bonus
And what if you want to change the logic for which the purchase price of tokens is considered? Of course, you can change it correctly
rate
or use one of the classes of contracts from OpenZeppelin, but what if you want something else more perverted? In a smart contract, you can oververt the function getTokenAmount
as follows:function_getTokenAmount(uint256 _weiAmount)
internalviewreturns (uint256)
{
if (block.timestamp < 1533081600) { // August 1st, 2018
rate = rate * 4;
} elseif (block.timestamp < 1546300800) { // January 1st, 2019
rate = rate * 2;
}
return _weiAmount.mul(rate);
}
In general, you can make the price of the token so dependent on the time of purchase - the farther into the forest, the more expensive the tokens. Don't be afraid to experiment and rewrite some of the functions of smart contracts - it's fun!
