(Part 18) Ethereum Solidity - ERC20 ICO, Conversion To ERC223 And "Fool-Proofing" The Contract(PT 18)

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)
image.png
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
image.png

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)
image.png

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
image.png

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
image.png
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

image.png

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
image.png
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
image.png

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
image.png

But when I try to send the token to the token contract, it fails (and is exactly what we wanted)
image.png

Curriculum

Beneficiaries

This post has as beneficiaries
@utopian.pay with 5%
using the SteemPeak beneficiary tool
image.png

H2
H3
H4
Upload from PC
Video gallery
3 columns
2 columns
1 column
5 Comments