Repository
https://github.com/igormuba/EthereumSolidityClasses/tree/master/class18
What Will I Learn?
- ICO of ERC20 token
- Introduction to ERC223 and transfer security
- Turning an ERC20 token into a 223
- Managing inter-contract token transaction
- Refunding tokens sent accidentally to our contract
- Making our token fool-proof (prevent users to send the token to incompatible address/contract)
Requirements
- Internet connection
- Code editor
- Browser
Difficulty
- Intermediate
Tutorial Contents
On the last tutorial I taught how to create a simple ICO with a simple token, but you won't be able to achieve much with a simple token, and most likely your simple token won't be accepted in many exchanges. On this tutorial, we will go one level further, and upgrade so that the ICO works with a more modern standard.
The ERC20 standard is covered more in-depth in another tutorial, so I won't go in depth about it, click here to read it if you want to understand the standard and the ERC20 we are using.
For this tutorial, I am using the ICO code from the last tutorial, and the token code from an older tutorial, I will give you the code first and teach you what changes must be done so the code made on the ERC20 tutorial can "talk" to the code made on the ICO tutorial, and from there on, the ICO will be able to actually sell "real" ERC20 tokens
Getting the code
You can get the code for an already made ERC20 token from this file on Github
https://github.com/igormuba/EthereumSolidityClass/blob/web3contract/FirstClass.sol
The code for the ICO is the same as the one from the last class, you can copy it from here
https://github.com/igormuba/EthereumSolidityClasses/blob/master/class17/ico.sol
I am going to use Remix browser IDE for Ethereum Solidity development, if you use the tutorials from the past classes about truffle, ganache, and local testnets, but since there is currently a bug with web3.js that fails to print some outputs, I prefer to skip the unnecessary part of troubleshooting for this tutorial. Remix works perfectly as a standalone IDE and can even deploy contracts to local test nets (like Ganache) and to the real and final Ethereum blockchain.
Modifying the ERC20 contract
I am making a few changes on the ERC20 contract, so it will have a few features from our previous "simple token" that can be used by the ICO. The functions were covered before, but they are below with comments explaining what do they do.
If you are using a
The first function is clearIcoBalance
, it is important to clear the balance of the creator ICO contract when it ends, so the remaining tokens are burned
function clearIcoBalance() public {
require(msg.sender == ICO); //requires that caller is the ICO contract
balances[msg.sender]=0; //cleans the balance of the sender
}
The second change we need to make is to pass the ICO contract to the ERC20 token constructor function
constructor(address _ICO) public{
And change so that the tokens created go to the ICO contract
ICO = _ICO
_totalSupply = 100; //sets the total supply
balances[ICO] = _totalSupply; //gives all the supply to the contract creator
Blocking ERC20 transfer
It is a widely used feature by ICO+ERC20 contracts: The tokens cannot be transferred from account to account until the ICO is over. This helps prevent "black market" of tokens before the ICO is over, which could reduce the crowdsale raised amount. However, on a post on his personal blog, Ethereum's creator, Vitalik Buterin, mentions a that he would rather see an ICO with flexible price and more dynamic economics, which implies in not locking the tokens until the ICO is over.
However, his model is a bit more complex, though I would love to talk about that in the future, I must first, introduce the traditional ERC20 ICOs.
To block the ERC20 transfers before the ICO ends is simple, we introduce a new variable
bool public icoOver = false;
And on the transfer functions, we add, before everything else, even before the other requirements
require(icoOver||msg.sender==ICO);
This will ensure the transfer only takes place if it is the ICO contract that is transferring (during ICO, because after the balance is cleaned) or the ICO has already ended.
The last thing is to change the clearIcoBalance
function so that it records on the icoOver
variable that the ICO has ended, and now transactions can take place
function clearIcoBalance() public {
require(msg.sender == ICO); //requires that caller is the ICO contract
balances[msg.sender]=0; //cleans the balance of the sender
icoOver=true;
}
Updating the ICO contract
On the ICO contract, all we need to do is to change what type of contract we are expecting to link, by changing the previous variable token
to
ERC20 public token;
And on the setToken
function
token=ERC20(_tokenAddress);
Deploying and testing
First, we need to deploy the ICO contract, as the token is dependent on the ICO
The Ethereum wallet (on the local testnet) used to deploy the contract (owner/creator) is 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
The address of the deployed contract is 0x038f160ad632409bfb18582241d9fd88c1a072ba
We pass it to the constructor of the ERC20 (the ERC20 standard does not, usually, require a standard token, it is a personalization we have made)
The address of the ERC20 contract deployed locally is 0xba8dc1a692093d8abd34e12aa05a4fe691121bb6
Back to the ICO contract, we, with the creator address, pass the ERC20 address to the function setToken
I have switched to the wallet 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
And I can buy the tokens normally, I have sent 10 Ethereum and my balance is 10 tokens (price is 1 token per ETH)
So far, it works as expected, the ICO contract can do transfers, but what about the buyer? We don't want people trading our tokens before the ICO ends
I will try to transfer 5 tokens to the address 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db
The transaction has failed and that is exactly what we wanted! To allow transfers we must end the ICO first
I have switched to the owner/creator account and called the endIco
function on the ICO contract.
The first test is to try to buy tokens with the buyer account
It has successfully failed(pun intended).
The second test is to see if the buyer can send tokens to the receiver 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db
And this time it works
ERC223 and security
We can't talk about ICOs without talking about security. First, I want to make sure you understand that the contracts we develop on the tutorials implement extremely basic security, and none of them should be used on production. I try to cover security as much as possible but I can't cover safe math, safety requirements, safe order at which things should happen, etc, on every tutorial, though I have covered all 3 topics mentioned above, just not on the same class.
On this section, the safety mechanism I want to talk about is the one used on the ERC223 standard, which tries to prevent tokens being sent to contracts that do not support their tokens. People often send tokens to addresses owned by contracts, and those contracts were not developed to work with those tokens, and as contracts are, by default, immutable (I have covered how to make mutable contracts), the tokens are literally lost forever with zero chances to recover them, unless a hardfork on the whole Ethereum blockchain happens.
It is important that developers try to take care of the user, not everyone is a developer, and if you know how blockchains and smart contracts work, you are lucky, we should be humble and try to understand other peoples lack knowledge in this regard and care for them.
This is a serious issue and thousands, maybe millions, of dollars were already lost by this kind of mistake. Don't take my word for it, if you look at the Basic Attention Token (BAT) address, you will see that the BAT contract has over 27 thousand of dollars worth of BAT on its own contract
But as ironic as it sounds, even though the tokens are stored on the token contract, they are lost forever!
This is because the BAT contract does not know how to handle tokens!
The ERC223 is very similar to the ERC20 standard, but it aims to fix exactly that.
Dealing with tokens sent to the token contract
A simple solution to revert transactions sent to the token contract (of whatever currency) is to make a fallback function that throws an error every time, so whenever someone sends Ethereum or any other token to the contract, it will fail and revert.
That can easily be done with
function() external payable{
require(false);
}
The function above is quite funny because it requires False to be True, so while the laws of physics, mathematics, and computing are intact, the contract is safe and we won't be responsible for people accidentally sending tokens to the token contract (who knows what they thought would happen by doing so)
How to know if the receiver is a contract
Our contract is already protecting users from their own lack of attention, but you, as a smart contract developer, should care for your user, you are building a piece of code that is (designed to be) immutable, so all care in the world is still not enough.
To try to protect our users from sending a transaction with tokens to a contract address that does not support tokens, we must implement a logic to check if the receiver is compatible (either a wallet or a contract that knows how to manage tokens), to do that you will need this piece of code
uint codeLength; //length of the code of the receiver
assembly {
codeLength := extcodesize(receiver) //checks the size of the code of the receiver and assigns to codeLength
}
if(codeLength>0){ //is the contract has code (like a smart contract does)
bytes memory empty; //only if the transfer function does not receive data, more on that below
ERC223Receiver erc223Receiver = ERC223Receiver(receiver); //sets the contract address
erc223Receiver.tokenFallback(msg.sender, amount, empty);//calls the ERC223 fallback function on the sender
}
Now, here is what we are doing above
extcodesize
this is an assembly code that sees if the address we pass to it has code inside it. Wallets never have code on it, so, even if you send tokens to a wallet of someone that does not know the token exists, the person can still recover, it is not lost forever, however, if you send to a contract, if the contract does not know what to do with the tokens, you can consider the tokens lost forever (unless the contract is designed to be mutable on the right pieces of code on the right way but that is totally out of scope of this class!).
bytes memory empty;
now, usually we have 2 transfer functions on our contracts, one that takes data, for whatever reason a third party contract or application might need data, and another that does not take data. If you are doing this on the function that does not take data you obviously need to send empty data to comply with the ERC223 standard, so we create an empty variable on memory just to send it, the receiver should know how to deal with it.
erc223Receiver.tokenFallback(msg.sender, amount, empty);
this effectively calls the default (according to the ERC223 standard) tokenFallback
on the receiver.
If you are working with the function that receives data as an argument, you want to send the data from the argument, not the empty data! In our case, we haven't implemented a function that deals with data on the ERC20 token, so we just need to empty one, but, in case you are working with a token that does deal with extra data (a game, for example!) for example, the following function
function transfer(address _to, uint _value, bytes memory _data) public{}
You should delete the bytes memory empty;
and change the tokenFallback
call to
receiver.tokenFallback(msg.sender, _value, _data);
The "interface"
So, before we have instantiated the RC223Receiver erc223Receiver = ERC223Receiver(receiver);
, but we haven't created the contract which our token will see the function, so, first, import the contract with
import "browser/ERC223Receiver.sol";
And create a new contract ERC223Receiver.sol
And now it is simple, just create a contract with a function with the default ERC223 naming for the receiver
pragma solidity ^0.5.0;
contract ERC223Receiver {
function tokenFallback(address _from, uint _value, bytes memory _data) public;
}
This piece of code is just so that our token knows what function to call from the receiver contract. If the receiver does not have this function, we will get an error and a refund, if the receiver has this function, he probably complies with ERC223 and should know how to deal with it, either refunding, like our contract does with a truly fallback function, or work with it with the standard ERC223 fallback function.
We won't implement logic to work with ERC223 though, because there would be a lot more to cover on that.
Testing
I won't go over the ICO deployment, token deployment, buying, ending the ICO etc...
Straight to the point:
I have tried to send tokens from a buyer to another wallet, and it worked
But when I try to send the token to the token contract, it fails (and is exactly what we wanted)
Curriculum
- (Part 17) Ethereum Solidity - ICO Contract, Basic Security And Token Interaction(PT 17)
- (Part 16) Ethereum Solidity - ERC721 Third Party Approval, Transfer, Events And Full Implementation(PT 16)
- (Part 15) Ethereum Solidity - ERC165, ERC721, Their Relation, Balances And Transfer Functions(PT 15)
- (Part 14) Ethereum Solidity - Token Uniqueness, Non-Fungibility And Transactions With Unique Tokens(PT 14)
- (Part 13) Ethereum Solidity In Truffle - Testnet Environment Function Calls And Truffle Testing(PT 13)
- (Part 12) Ethereum Solidity - Using Truffle, Ganache And Zeppelin To Deploy(PT 12)
- (Part 11) Ethereum Solidity - Multisig Contract As Bank,k Multiple Users, And Where To Implement App Logic(PT 11)
- (Part 10) Ethereum Solidity - Multiple inheritances, Diamond Problem And Function Polymorphism(PT 10)
- (Part 9) Ethereum Solidity Assembly - Return, Memory, Hexadecimal, Pointers, And Memory Addressing(PT 9)
- (Part 8) Ethereum Solidity - Assembly, Reducing Costs And Creation Your Low-Level Expression(PT 8)
- (Part 7) Ethereum Solidity - Fallback, Ethereum Fractions, And Collateral Backed Contract(PT 7)
- (Part 6) Ethereum Solidity - Custom Variable Functionalities, Libraries, Using Libraries For Security(PT 6)
- (Part 5) Ethereum Solidity - Custom Access Modifiers, Security Breach Alerts, Assert And Require(PT 5)
- (Part 4) Ethereum Solidity Development - Inheritance, Working With Multiple Contracts And Simple Mutability(PT 4)
- (Part 3) Ethereum Solidity Development - Contract Mutability, DelegateCall And Calling Functions By Address(PT 3)
- (Part 2) Ethereum Solidity Development - Deploying, Security And ERC20 Compliance(PT 2)
- (Part 1) Ethereum Solidity Development - Getting Started + Lower Level Explanation (PT 1)
Beneficiaries
This post has as beneficiaries
@utopian.pay with 5%
using the SteemPeak beneficiary tool