After choosing the best IDE to develop our first smart contract, we will now write some actual code.
This is part three of a series on how to create an ERC-20 token. If you want to begin from scratch, please read part one and part two of this series (part four and part five are right here).
Using Ethereum Studio, we began using the ‘Hello World’ template to understand the basics of Solidity. This is the ‘Hello World’ template:
pragma solidity ^0.5.10;
contract HelloWorld {
string public message;
constructor(string memory initMessage) public {
message = initMessage;
}
function update(string memory newMessage) public {
message = newMessage;
}
}
This says that the smart contract HelloWorld initializes a message that is passed as a parameter and which can be updated using the update function. Please remember that the newMessage variable will be the message variable for the most updated block, but one can view all message variables in previous blocks: this is known as a State Variable. Every variable in a smart contract is a variable in the block it gets executed into. All variables in a given block compose the State of the blockchain, along with all the ‘normal’ transactions.
Functions are the same as in every other programming language, and can accept parameters and return variables.
We will now explore the development of a simple contract for implementing the simplest form of cryptocurrency. It allows the creation of new coins (minting), and sending them to addresses belonging to the same blockchain (transactions between assets in two different blockchain are possible, but require an extra layer that will not be discussed in this series).
In order to do implement this form, we can edit the current situation by pasting this code and renaming the file to ExampleCoin.sol:
pragma solidity >=0.5.0 <0.7.0;
contract ExampleCoin {
address public minter;
mapping (address => uint) public balances;
event Sent(address from, address to, uint amount);
constructor() public {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
require(amount < 1e60);
balances[receiver] += amount;
}
function send(address received, uint amount) public {
require(amount <= balances[msg.sender], "Insufficient balance.");
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
The contract above is very simple: it creates an ExampleCoin token (also referred to as subcurrency in Ethereum Studio) by initializing a coin minter (with the public constructor), i.e., an address that can create new coins. This is also the address of the contract creator, so only the creator can mint new coins (see the require(msg.sender == minter) instruction). This contract also models the mapping of every address with its balance, and the function send that simply checks the feasibility of the transaction. If the sender has less balance that they want to send, the contract will return an Insufficient balance statement and the transaction will not be completed. If the transaction is feasible, the function will emit a send event – a declaration for the broadcast of the transactions. Ethereum clients (wallets or decentralized apps on the web) can listen to these events emitted on the blockchain without much cost. As soon as the event is emitted, the listener receives the arguments from, to, and amount, which makes it possible to track transactions.
Keep in mind: to approve any transaction, the client (wallet or decentralized app on the web) must be synced with the State of the blockchain, i.e., it must know what the most recent state of all the variables in the blockchain is. A smart contract executed offline will not verify the requirements of the transactions until it’s back online and fully synced with the blockchain.
The minter and every address mapped are State Variables. Specifically, this mapping can be thought of as hash tables, virtually initialized such that every possible key exists from the start and is mapped to a value whose byte-representation is all zeros.
It’s impossible to get a list of all of a mapping’s keys, or a list of all values, so it’s good practice to either record what you added to the mapping (if it’s useful for other functions), or to keep a list of all the changes recorded by the contract.
To get the balance of an address, the balances function can be used at any time:
function balances(address _account) external view returns (uint) {
return balances[_account];
}
The main ExampleCoin.sol file is now complete (for the moment), but to make this contract usable, we need to give it the capacity for interaction. The .sol file is intended to be the backend logic of the contract, but there’s also a frontend logic that needs to be implemented. This logic can be developed with the Web3 technology stack, and that will be the main focus of the next chapter in this series.