(Part 2) Ethereum Solidity Development - Deploying, Securiy And ERC20 Compliance(PT 2)
Repository
https://github.com/igormuba/EthereumSolidityClass/tree/web3contract
What Will I Learn?
- Ethereum events
- Ethereum functions
- ERC20 compliance
- Start a test environment on Node
Requirements
State the requirements the user needs in order to follow this tutorial.
- Internet connection
- Code editor
- Browser
Difficulty
- Intermediate
Tutorial Contents
Let us start our private Ethereum blockchain so we can learn on a more realistic environment instead of testing functions on Remix, with this environment we can use the Metamask later and develop real web applications, like test ICOs and practice crypto kitties like apps.
Assuming you are a linux user, like me, first you need node package manager, you can get it by going into the terminal and telling it
sudo apt install npm
If you are using Windows or Mac you can get NPM with NodeJS here
https://nodejs.org/en/
Now, let us install globally the Ethereum test network, this network will be local on your machine but will behave by any means necessary just like the real Ethereum blockchain.
npm install -g ethereumjs-testrpc
Now we launch the Ethereum testing network
testrpc
Notice that the testing environment has created 10 Ethereum adresses with Ethereum inside them, this is, of course, not real Ethereum, but will do the job for your tests :)
Wish it were real though haha
Now we can start to interact with this local environment, note that the real costs for a real application might be different, but the test network is more powerful than Remix online IDE.
Let us also install web3 so we can develop browser applications later
npm install -g [email protected]
Initiating the Node directory
Inside the directory of the project do
npm init
And
npm install web3
Now open the node console by typping in the terminal
node
And in the console do
Web3 = require('web3')
then
provider = new Web3.providers.HttpProvider("http://localhost:8545")
Notice that if you have changed the port or you are doing on a remote server or any other personalized setting you can see what is the address that should be passed as a string argument to the HttpProvider
function on the console where you have openned the testrpc
And, last,
web3 = new Web3(provider)
Now we have web3 on our project.
On the testrpc, please, copy the wallet that you were given, you will use them to transfer tokens around
In my case, the accounts I have got are
(0) 0xfe7f63e4a14151e4275e429341714b500db8445d
(1) 0xed38a2696c6ce473e820ea7974d4fb0884effb96
(2) 0x7a9ea8150c1640fb62eab4eca340620e28703f62
(3) 0xa400bae51c74c788aa01d513a06190d15fec1518
(4) 0x57efc776efdf76c93cfc4ab8ba7ba350048fc690
(5) 0x14e8249a479f95d649d818fc44f6a703a4c13e5a
(6) 0x014dbe37e198ef993406beb0075e335de32bb1ae
(7) 0x39e365a77fb2b32faf0b2ca90c4cd47edb6011fa
(8) 0xd2cc66f827e1c5c43a31e9d5f842582717b72702
(9) 0xaa41a595476ea690860eb9f0c447aaaad3498dcf
A Simple Token Contract
This is the code from the last class
pragma solidity ^0.5.0;
import /usr/local/lib/node_modules/web3/;
provider = new Web3.providers.HttpProvider("http://localhost:8545")
web3 = new Web3(provider)
contract firstClassContract{
string private stringVariable = "Variable name";
int private unsignedInteger = 10;
function setStringVariable(string memory newVariable) public {
stringVariable = newVariable;
}
function getStringVariable() public view returns (string memory){
return stringVariable;
//test
}
}
Let us change it a little bit to turn it into an actually useful contract.
First, remove the variables stringVariable
and unsignedInteger
We want to specify who is the creator of the contract, it is useful because we will set his balance, we can do it with
address public creator; //address of contract creator to be defined
This looks like a variable but we will have to make sure this variable can't be changed. So we will use it only on the constructor function. Be sure to avoid using this variable anywhere else.
uint256 public totalSupply; //total coin supply
mapping (address => uint256) public balances; //like a dictionary, an address represents
// a positive integer
The above is what really turns this contract into a coin contract.
You can notice that the coins are nothing more than a number inside a variable, yet some numbers inside some functions inside some contracts are very valuable. Kinda frustrating, right?
Constructor
The constructor function is called only once and only when the contract is created. This can ensure that some variables can't be "attacked", for example, we don't want other people to hijack our contract.
function firstClassContract() public{
creator = msg.sender; //the creator of the contract
//msg.sender is automatically generated on contract deploy
totalSupply = 100; //sets the total supply
balances[creator] = totalSupply; //gives all the supply to the contract creator
}
This is very simple for now.
Tranfering our tokens
First, we will set a function to get the balance of the person that is calling it. It is a view function, it means it only reads data, does not write anything. Be very careful with function types.
function balanceOf(address memory owner) public view returns(uint256 memory){
return balances[owner];
}
Now this is a very common send function.
I have adapted it to use the memory declaration to save resources. You can read about it at
https://blog.zeppelin.solutions/a-gentle-introduction-to-ethereum-programming-part-2-7bbf15e1a953
function sendTokens(address memory receiver, uint256 memory amount)
public returns(bool){
address owner = msg.sender; //this is the caller of the function
require(amount > 0); //the caller has to have a balance of more than zero tokens
require(balances[owner] >= amount); //the balance must be bigger than the transaction amount
balances[owner] -= amount; //deduct from the balance of the sender first
balances[receiver] += amount; //add to the balance of the receiver after
return true;
}
}
NOTE: Whenever you are making a function that does transfer of tokens you need to deduct from the sender before you add to the balance of the receiver. The DAO was hacked because of a function that added funds to the receiver before checking if the sender had the amount.
You can read more about the exploit used on the attack here
http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/
So, if you want to evolve into a blockchain developer you must study 3 timer harder security and data integrity than you study real development. Any mistake can be very expensive!
Now you have a very basic contract that does the basic. But it does not comply to any token standard
The ERC20 standard
The ERC20 standard for token contracts is a design pattern for creating tokens on the Ethereum network and make integration with third parties easy.
For example, by using the ERC20 you can create a token and right away use it in many already well stablished wallets and even descentralized Ethereum based exchanges because if your contract follows the design pattern those wallets and exchanges already know what functions to call and how to work with your coin. So knowing what design pattern to use is very important.
According to Wikipedia
https://en.wikipedia.org/wiki/ERC-20
To comply with the ERC20 standard your token must implement at the following functions (it can have more functions and functionalities, though)
- totalSupply() public view returns (uint256 totalSupply)
- balanceOf(address _owner) public view returns (uint256 balance)
- transfer(address _to, uint256 _value) public returns (bool success)
- transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
- approve(address _spender, uint256 _value) public returns (bool success)
- allowance(address _owner, address _spender) public view returns (uint256 remaining)
But also, according to the Ethereum Improvement Proposal available at
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
There are other requirements, like giving a name, symbol and decimals, though the decimals is optional.
Let us make our token ERC20 compliant.
First, create the variables with name and symbol and set their values on the constructor.
Declaring them outside the constructor
string public _tokenName;
string public _tokenSymbol;
In the beginning of the constructor
function firstClassContract() public{
_tokenName = "ERC20 Token"; //sets the name of the token
_tokenSymbol = "ERCT" //ticker symbol of the token
//[... more content below hidden for better visualization]
ERC20 events
Solidity implements events, that behave like callback functions, and are very useful for us to update the user interface without having to actively reload the page. We are not yet implementing any user interface, but, supposing this was a real contract for a real project, some third party interface could want to use our token, so they would be able to work with our contract and update the interface without having to check the blockchain for updates, because the callback event would alert that something has changed, making them, and us, literally save money! So let us define those events to make our contract ERC20 compliant
The event are very straight forward
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
Both of them receive the from, where and amount of tokens transfered and alert the sender that the transfer happened.
The ERC20 functions
balanceOf(we already have it)
We already have the balanceOf function, let us do the total supply.
totalSupply
Remember the class about view
vs pure
? There is 2 ways you could implement this function. One is kinda cheating. The first and "right" way to do this is by using view and really checking on the blockchain what is the total supply of the coin.
function totalSupply() public view returns (uint256 memory) {
return totalSupply;
}
The second and "cheating" way to do it (DON'T TELL ANYONE I TAUGHT YOU THAT!) is to use a pure
function, this one is cheaper to execute since it does not check the blockchain.
function totalSupply() public pure returns (uint256 memory) {
return 100; //hardcode it to return the total supply from the constructor
}
The last solution is hardcoded and a bad practice, but for all effects it works the same and is cheaper to execute! Again DON'T TELL I TAUGHT YOU THAT
transfer
We have a very similar function on our contract already, but WE NEED TO CHANGE THE NAME as the very reason of ERC20 is to make the working of many contracts similar so third party can use them without even checking the code, as they should know what functions to call to do anything, so, change the name of the function sendTokens
to transfer
and call it a day. Don't work harder, work smarted ;)
allowance
To make the next function work we need to add a mapping of what addresses can spend from other, this allows for delegation of the right to spend tokens from your account
So, create hte variable
mapping (address => mapping (address => uint256)) private _allowed;
The above is like a dictionary with the list of all accounts that are allowed to spend from what accounts
Now, for the function
function allowance(address owner, address spender) public view returns (uint256){
return _allowed[owner][spender]; //adds to the allowance mapping
}
Will add the addresses to the mapping above
approve
This works together with the next function and is used to allow third parties to spend your tokens, this is like delegating the right to use your wallet to someone else
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
If you are going to use this code for production purpouses, a good read, recommended by the creators of the ERC20 standard, about attack vectors are
https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit
And
https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
transferFrom
This is one of the functions that make the ERC20 special, this function allows third parties (contracts for example) to make transfers on your behalf,
We can tweak the transfer function
This is the transfer function
function transfer(address memory receiver, uint256 memory amount)
public returns(bool){
address owner = msg.sender; //this is the caller of the function
require(amount > 0); //the caller has to have a balance of more than zero tokens
require(balances[owner] >= amount); //the balance must be bigger than the transaction amount
balances[owner] -= amount; //deduct from the balance of the sender first
balances[receiver] += amount; //add to the balance of the receiver after
return true;
}
}
Copy and create one identical to the above, but add on the arguments it receive a sender, before the receiver
so it becomes
function transferFrom(address memory sender, address memory receiver, uint256 memory amount)
And add a condition inside it using the require
require(_value <= allowance[_from][msg.sender]); //requires that the caller of the function has the permission to send this value from the sender of the amount
Also, inside the function, there are 2 other places that we need to change the owner
into sender
require(balances[sender] >= amount); //the balance must be bigger than the transaction amount
balances[sender] -= amount; //deduct from the balance of the sender first
In the end, a simple ERC20 compliant token
In the end the code, with all fixes and changes, should look like this (feel free to copy, but don't use this for production, only use for learning purpouses as it does not implement safe math and a few other security measures!!)
pragma solidity ^0.5.0;
import /usr/local/lib/node_modules/web3/;
provider = new Web3.providers.HttpProvider("http://localhost:8545")
web3 = new Web3(provider)
contract firstClassContract{
string public _tokenName;
string public _tokenSymbol;
address public creator; //address of contract creator to be defined
uint256 public _totalSupply; //total coin supply
mapping (address => mapping (address => uint256)) private _allowed; //allowance
mapping (address => uint256) public balances; //like a dictionary, an address represents
// a positive integer
// new variables can't be created
//the above ones are created during contract creation
//below is the constructor function called only when the contract is created
constructor() public{
_tokenName = "ERC20 Token"; //sets the name of the token
_tokenSymbol = "ERCT"; //ticker symbol of the token
creator = msg.sender; //the creator of the contract
//msg.sender is automatically generated on contract deploy
_totalSupply = 100; //sets the total supply
balances[creator] = _totalSupply; //gives all the supply to the contract creator
}
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function balanceOf(address owner) public view returns(uint256){
return balances[owner];
}
function allowance(address owner, address spender) public view returns (uint256){
return _allowed[owner][spender]; //adds to the allowance mapping
}
function transfer(address receiver, uint256 amount)
public returns(bool){
address owner = msg.sender; //this is the caller of the function
require(amount > 0); //the caller has to have a balance of more than zero tokens
require(balances[owner] >= amount); //the balance must be bigger than the transaction amount
balances[owner] -= amount; //deduct from the balance of the sender first
balances[receiver] += amount; //add to the balance of the receiver after
return true;
}
function transferFrom(address sender, address receiver, uint256 amount)
public returns(bool){
require(amount <= _allowed[sender][msg.sender]); //requires that the caller of the function has the permission to send this value from the sender of the amount
address owner = msg.sender; //this is the caller of the function
require(amount > 0); //the caller has to have a balance of more than zero tokens
require(balances[sender] >= amount); //the balance must be bigger than the transaction amount
balances[sender] -= amount; //deduct from the balance of the sender first
balances[receiver] += amount; //add to the balance of the receiver after
return true;
}
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
}
If we put that on remix, first remove the
import /usr/local/lib/node_modules/web3/;
provider = new Web3.providers.HttpProvider("http://localhost:8545")
web3 = new Web3(provider)
so we can compile and deploy to test
Thank you for your contribution @igormuba.
Good work on developing this tutorial. It's good that you put the results of each development you make.
We are waiting for your next tutorial.
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Thank you for your review, @portugalcoin! Keep up the good work!
Hi @igormuba!
Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server
Hey, @igormuba!
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!